Compare commits
13
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ddd07464d4 | ||
|
|
d7ba0b3215 | ||
|
|
98b25ee17e | ||
|
|
99106a8a73 | ||
|
|
b56d0fb8f2 | ||
|
|
f6f692f38e | ||
|
|
8009e61fc5 | ||
|
|
4fa8a63c71 | ||
|
|
8c8a8a9c22 | ||
|
|
2e12a37cb0 | ||
|
|
e97e674ca1 | ||
|
|
6c91cfcb66 | ||
|
|
34cab94692 |
No files matched your search
+16
-30
@@ -207,23 +207,17 @@ dotnet_naming_symbols.parameters.applicable_accessibilities = public,internal,pr
|
|||||||
dotnet_naming_symbols.private_symbols.applicable_kinds = property,method,field,event,delegate
|
dotnet_naming_symbols.private_symbols.applicable_kinds = property,method,field,event,delegate
|
||||||
dotnet_naming_symbols.private_symbols.applicable_accessibilities = protected,private
|
dotnet_naming_symbols.private_symbols.applicable_accessibilities = protected,private
|
||||||
|
|
||||||
# local symbols
|
|
||||||
dotnet_naming_symbols.local_symbols.applicable_kinds = local
|
|
||||||
|
|
||||||
# parameter symbols
|
|
||||||
dotnet_naming_symbols.parameter_symbols.applicable_kinds = parameter
|
|
||||||
|
|
||||||
# Naming styles
|
# Naming styles
|
||||||
|
|
||||||
dotnet_naming_style.pascal_case.required_prefix =
|
dotnet_naming_style.snake_case.required_prefix =
|
||||||
dotnet_naming_style.pascal_case.required_suffix =
|
dotnet_naming_style.snake_case.required_suffix =
|
||||||
dotnet_naming_style.pascal_case.word_separator =
|
dotnet_naming_style.snake_case.word_separator = _
|
||||||
dotnet_naming_style.pascal_case.capitalization = pascal_case
|
dotnet_naming_style.snake_case.capitalization = all_lower
|
||||||
|
|
||||||
dotnet_naming_style.camel_case.required_prefix =
|
dotnet_naming_style.private_snake_case.required_prefix = _
|
||||||
dotnet_naming_style.camel_case.required_suffix =
|
dotnet_naming_style.private_snake_case.required_suffix =
|
||||||
dotnet_naming_style.camel_case.word_separator =
|
dotnet_naming_style.private_snake_case.word_separator = _
|
||||||
dotnet_naming_style.camel_case.capitalization = camel_case
|
dotnet_naming_style.private_snake_case.capitalization = all_lower
|
||||||
|
|
||||||
dotnet_naming_style.user_types.required_prefix =
|
dotnet_naming_style.user_types.required_prefix =
|
||||||
dotnet_naming_style.user_types.required_suffix =
|
dotnet_naming_style.user_types.required_suffix =
|
||||||
@@ -235,10 +229,6 @@ dotnet_naming_style.interface.required_suffix =
|
|||||||
dotnet_naming_style.interface.word_separator =
|
dotnet_naming_style.interface.word_separator =
|
||||||
dotnet_naming_style.interface.capitalization = pascal_case
|
dotnet_naming_style.interface.capitalization = pascal_case
|
||||||
|
|
||||||
dotnet_naming_style.local.capitalization = camel_case
|
|
||||||
|
|
||||||
dotnet_naming_style.parameter.capitalization = camel_case
|
|
||||||
|
|
||||||
# rules
|
# rules
|
||||||
dotnet_naming_rule.user_types.symbols = user_types
|
dotnet_naming_rule.user_types.symbols = user_types
|
||||||
dotnet_naming_rule.user_types.style = user_types
|
dotnet_naming_rule.user_types.style = user_types
|
||||||
@@ -248,18 +238,14 @@ dotnet_naming_rule.interfaces.symbols = interface
|
|||||||
dotnet_naming_rule.interfaces.style = interface_style
|
dotnet_naming_rule.interfaces.style = interface_style
|
||||||
dotnet_naming_rule.interfaces.severity = suggestion
|
dotnet_naming_rule.interfaces.severity = suggestion
|
||||||
|
|
||||||
dotnet_naming_rule.public_members.symbols = public_symbols
|
dotnet_naming_rule.public_members_snake_case.symbols = public_symbols
|
||||||
dotnet_naming_rule.public_members.style = pascal_case
|
dotnet_naming_rule.public_members_snake_case.style = snake_case
|
||||||
dotnet_naming_rule.public_members.severity = suggestion
|
dotnet_naming_rule.public_members_snake_case.severity = suggestion
|
||||||
|
|
||||||
dotnet_naming_rule.private_members.symbols = private_symbols
|
dotnet_naming_rule.private_members_snake_case.symbols = private_symbols
|
||||||
dotnet_naming_rule.private_members.style = camel_case
|
dotnet_naming_rule.private_members_snake_case.style = private_snake_case
|
||||||
dotnet_naming_rule.private_members.severity = suggestion
|
dotnet_naming_rule.private_members_snake_case.severity = suggestion
|
||||||
|
|
||||||
dotnet_naming_rule.variables_are_camel_case.severity = suggestion
|
dotnet_naming_rule.parameters.symbols = parameters
|
||||||
dotnet_naming_rule.variables_are_camel_case.symbols = local_symbols
|
dotnet_naming_rule.parameters.style = snake_case
|
||||||
dotnet_naming_rule.variables_are_camel_case.style = local
|
|
||||||
|
|
||||||
dotnet_naming_rule.parameters.symbols = parameter_symbols
|
|
||||||
dotnet_naming_rule.parameters.style = parameter
|
|
||||||
dotnet_naming_rule.parameters.severity = suggestion
|
dotnet_naming_rule.parameters.severity = suggestion
|
||||||
+2
-2
@@ -47,8 +47,8 @@ trace_data/
|
|||||||
/src/.clang-format
|
/src/.clang-format
|
||||||
/src/server/sparkd/cmds/spartan/analysis/.ipynb_checkpoints
|
/src/server/sparkd/cmds/spartan/analysis/.ipynb_checkpoints
|
||||||
/src/server/sparkd/cmds/spartan/analysis/data
|
/src/server/sparkd/cmds/spartan/analysis/data
|
||||||
.tmp/
|
*.tmp/*
|
||||||
.venv*/
|
/.venv*/*
|
||||||
.cache/
|
.cache/
|
||||||
/src/Spark.Admin.WebApp/tsconfig.*.g.json
|
/src/Spark.Admin.WebApp/tsconfig.*.g.json
|
||||||
/src/Spark.Admin.WebApp/src/bin/
|
/src/Spark.Admin.WebApp/src/bin/
|
||||||
|
|||||||
+3
-3
@@ -29,8 +29,8 @@
|
|||||||
"CMAKE_TOOLCHAIN_FILE": "${sourceDir}/vcpkg/scripts/buildsystems/vcpkg.cmake",
|
"CMAKE_TOOLCHAIN_FILE": "${sourceDir}/vcpkg/scripts/buildsystems/vcpkg.cmake",
|
||||||
"EXTERNALS_ROOT": "${sourceDir}/externals/windows",
|
"EXTERNALS_ROOT": "${sourceDir}/externals/windows",
|
||||||
"CONFIG_DEFAULTS_PATH": "config/profiles/default/*.conf",
|
"CONFIG_DEFAULTS_PATH": "config/profiles/default/*.conf",
|
||||||
"DOTNET_BINARY_DIR": "${sourceDir}/build/$<CONFIG>/bin/net8.0",
|
"DOTNET_BINARY_DIR": "${sourceDir}/build/$<CONFIG>/bin/net7.0",
|
||||||
"DOTNET_WINDOWS_BINARY_DIR": "${sourceDir}/build/$<CONFIG>/bin/net8.0-windows"
|
"DOTNET_WINDOWS_BINARY_DIR": "${sourceDir}/build/$<CONFIG>/bin/net7.0-windows"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -197,7 +197,7 @@
|
|||||||
"CMAKE_CXX_FLAGS_RELEASE": "-fPIC -Wall -pedantic -Wextra -Werror -Wno-missing-braces -Wno-ignored-qualifiers --std=c++20 -fdiagnostics-color -fno-omit-frame-pointer -DNDEBUG -O3",
|
"CMAKE_CXX_FLAGS_RELEASE": "-fPIC -Wall -pedantic -Wextra -Werror -Wno-missing-braces -Wno-ignored-qualifiers --std=c++20 -fdiagnostics-color -fno-omit-frame-pointer -DNDEBUG -O3",
|
||||||
"CMAKE_INSTALL_PREFIX": "${sourceDir}/install/${presetName}",
|
"CMAKE_INSTALL_PREFIX": "${sourceDir}/install/${presetName}",
|
||||||
"CONFIG_DEFAULTS_PATH": "config/profiles/default/*.conf",
|
"CONFIG_DEFAULTS_PATH": "config/profiles/default/*.conf",
|
||||||
"DOTNET_BINARY_DIR": "${sourceDir}/build/$<CONFIG>/bin/net8.0"
|
"DOTNET_BINARY_DIR": "${sourceDir}/build/$<CONFIG>/bin/net7.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ RUN apt-get update && apt-get install -y \
|
|||||||
sqlite3 \
|
sqlite3 \
|
||||||
tzdata
|
tzdata
|
||||||
|
|
||||||
# clang 13, 15 (so we can build old branches)
|
# clang
|
||||||
RUN apt-get update && apt-get install -y \
|
RUN apt-get update && apt-get install -y \
|
||||||
clang-13 \
|
clang-13 \
|
||||||
clang-format-13 \
|
clang-format-13 \
|
||||||
@@ -25,19 +25,6 @@ RUN apt-get update && apt-get install -y \
|
|||||||
clang-format-15 \
|
clang-format-15 \
|
||||||
clangd-15
|
clangd-15
|
||||||
|
|
||||||
# clang 17 (so we can build new branches)
|
|
||||||
RUN \
|
|
||||||
curl -fsSL https://apt.llvm.org/llvm-snapshot.gpg.key 2> /dev/null \
|
|
||||||
| gpg --dearmor - \
|
|
||||||
| tee /usr/share/keyrings/llvm-snapshot.gpg > /dev/null \
|
|
||||||
&& echo "deb [signed-by=/usr/share/keyrings/llvm-snapshot.gpg] http://apt.llvm.org/jammy/ llvm-toolchain-jammy-17 main" \
|
|
||||||
| tee -a "/etc/apt/sources.list.d/llvm.list" \
|
|
||||||
&& apt-get update \
|
|
||||||
&& apt-get install -y \
|
|
||||||
clang-17 \
|
|
||||||
clang-format-17 \
|
|
||||||
clangd-17
|
|
||||||
|
|
||||||
# g++
|
# g++
|
||||||
RUN apt-get update && apt-get install -y \
|
RUN apt-get update && apt-get install -y \
|
||||||
g++-11 \
|
g++-11 \
|
||||||
@@ -50,12 +37,8 @@ RUN apt-get update && apt-get install -y \
|
|||||||
&& python3 -m pip install --upgrade pip
|
&& python3 -m pip install --upgrade pip
|
||||||
|
|
||||||
# dotnet
|
# dotnet
|
||||||
RUN \
|
RUN apt-get update && apt-get install -y \
|
||||||
curl -fsSLO "https://packages.microsoft.com/config/ubuntu/$(lsb_release -rs)/packages-microsoft-prod.deb" \
|
dotnet7
|
||||||
&& dpkg -i packages-microsoft-prod.deb \
|
|
||||||
&& rm packages-microsoft-prod.deb \
|
|
||||||
&& apt-get update \
|
|
||||||
&& apt-get install -y dotnet-sdk-8.0
|
|
||||||
|
|
||||||
# ssh
|
# ssh
|
||||||
RUN apt-get update && apt-get install -y \
|
RUN apt-get update && apt-get install -y \
|
||||||
@@ -70,6 +53,10 @@ RUN \
|
|||||||
&& echo "deb [signed-by=/usr/share/keyrings/kitware-archive-keyring.gpg] https://apt.kitware.com/ubuntu/ $(lsb_release -cs) main" \
|
&& echo "deb [signed-by=/usr/share/keyrings/kitware-archive-keyring.gpg] https://apt.kitware.com/ubuntu/ $(lsb_release -cs) main" \
|
||||||
| tee /etc/apt/sources.list.d/kitware.list > /dev/null \
|
| tee /etc/apt/sources.list.d/kitware.list > /dev/null \
|
||||||
&& apt-get update \
|
&& apt-get update \
|
||||||
|
&& rm /usr/share/keyrings/kitware-archive-keyring.gpg \
|
||||||
|
&& apt-get install -y kitware-archive-keyring \
|
||||||
|
&& echo "deb [signed-by=/usr/share/keyrings/kitware-archive-keyring.gpg] https://apt.kitware.com/ubuntu/ $(lsb_release -cs)-rc main" \
|
||||||
|
| tee -a /etc/apt/sources.list.d/kitware.list >/dev/null \
|
||||||
&& apt-get install -y cmake
|
&& apt-get install -y cmake
|
||||||
|
|
||||||
# node
|
# node
|
||||||
@@ -108,7 +95,6 @@ RUN apt-get update && apt-get install -y \
|
|||||||
# dev utils
|
# dev utils
|
||||||
RUN apt-get update && apt-get install -y \
|
RUN apt-get update && apt-get install -y \
|
||||||
fish \
|
fish \
|
||||||
gdb \
|
|
||||||
git-all \
|
git-all \
|
||||||
sudo \
|
sudo \
|
||||||
vim \
|
vim \
|
||||||
@@ -116,12 +102,12 @@ RUN apt-get update && apt-get install -y \
|
|||||||
|
|
||||||
# set up default executables (update-alternatives)
|
# set up default executables (update-alternatives)
|
||||||
RUN \
|
RUN \
|
||||||
update-alternatives --install /usr/bin/clang++ clang++ /usr/bin/clang++-17 100; \
|
update-alternatives --install /usr/bin/clang++ clang++ /usr/bin/clang++-13 100; \
|
||||||
update-alternatives --install /usr/bin/clang clang /usr/bin/clang-17 100; \
|
update-alternatives --install /usr/bin/clang clang /usr/bin/clang-13 100; \
|
||||||
update-alternatives --install /usr/bin/clang-format clang-format /usr/bin/clang-format-17 100; \
|
update-alternatives --install /usr/bin/clang-format clang-format /usr/bin/clang-format-13 100; \
|
||||||
update-alternatives --install /usr/bin/clangd clangd /usr/bin/clangd-17 100; \
|
update-alternatives --install /usr/bin/clangd clangd /usr/bin/clangd-13 100; \
|
||||||
update-alternatives --install /usr/bin/g++ g++ /usr/bin/g++-12 100; \
|
update-alternatives --install /usr/bin/g++ g++ /usr/bin/g++-11 100; \
|
||||||
update-alternatives --install /usr/bin/gcc gcc /usr/bin/gcc-12 100
|
update-alternatives --install /usr/bin/gcc gcc /usr/bin/gcc-11 100
|
||||||
|
|
||||||
# Add compiler symlinks for ccache
|
# Add compiler symlinks for ccache
|
||||||
RUN \
|
RUN \
|
||||||
@@ -131,8 +117,6 @@ RUN \
|
|||||||
ln -s /usr/bin/ccache /usr/local/bin/clang++-13; \
|
ln -s /usr/bin/ccache /usr/local/bin/clang++-13; \
|
||||||
ln -s /usr/bin/ccache /usr/local/bin/clang-15; \
|
ln -s /usr/bin/ccache /usr/local/bin/clang-15; \
|
||||||
ln -s /usr/bin/ccache /usr/local/bin/clang++-15; \
|
ln -s /usr/bin/ccache /usr/local/bin/clang++-15; \
|
||||||
ln -s /usr/bin/ccache /usr/local/bin/clang-17; \
|
|
||||||
ln -s /usr/bin/ccache /usr/local/bin/clang++-17; \
|
|
||||||
ln -s /usr/bin/ccache /usr/local/bin/gcc; \
|
ln -s /usr/bin/ccache /usr/local/bin/gcc; \
|
||||||
ln -s /usr/bin/ccache /usr/local/bin/g++; \
|
ln -s /usr/bin/ccache /usr/local/bin/g++; \
|
||||||
ln -s /usr/bin/ccache /usr/local/bin/gcc-11; \
|
ln -s /usr/bin/ccache /usr/local/bin/gcc-11; \
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
<Project Sdk="Microsoft.NET.Sdk">
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<TargetFramework>net8.0</TargetFramework>
|
<TargetFramework>net7.0</TargetFramework>
|
||||||
<ImplicitUsings>enable</ImplicitUsings>
|
<ImplicitUsings>enable</ImplicitUsings>
|
||||||
<Nullable>enable</Nullable>
|
<Nullable>enable</Nullable>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
<Project Sdk="Microsoft.NET.Sdk">
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<TargetFramework>net8.0</TargetFramework>
|
<TargetFramework>net7.0</TargetFramework>
|
||||||
<ImplicitUsings>enable</ImplicitUsings>
|
<ImplicitUsings>enable</ImplicitUsings>
|
||||||
<Nullable>enable</Nullable>
|
<Nullable>enable</Nullable>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
<Project Sdk="Microsoft.NET.Sdk">
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<TargetFramework>net8.0-windows</TargetFramework>
|
<TargetFramework>net7.0-windows</TargetFramework>
|
||||||
<ImplicitUsings>enable</ImplicitUsings>
|
<ImplicitUsings>enable</ImplicitUsings>
|
||||||
<Nullable>enable</Nullable>
|
<Nullable>enable</Nullable>
|
||||||
<UseWindowsForms>true</UseWindowsForms>
|
<UseWindowsForms>true</UseWindowsForms>
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
<Project Sdk="Microsoft.NET.Sdk">
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<TargetFramework>net8.0</TargetFramework>
|
<TargetFramework>net7.0</TargetFramework>
|
||||||
<ImplicitUsings>enable</ImplicitUsings>
|
<ImplicitUsings>enable</ImplicitUsings>
|
||||||
<Nullable>enable</Nullable>
|
<Nullable>enable</Nullable>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
<Project Sdk="Microsoft.NET.Sdk">
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<TargetFramework>net8.0</TargetFramework>
|
<TargetFramework>net7.0</TargetFramework>
|
||||||
<ImplicitUsings>enable</ImplicitUsings>
|
<ImplicitUsings>enable</ImplicitUsings>
|
||||||
<Nullable>enable</Nullable>
|
<Nullable>enable</Nullable>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|||||||
@@ -1,119 +0,0 @@
|
|||||||
namespace Bts.DataStream.Websocket;
|
|
||||||
|
|
||||||
using BCG;
|
|
||||||
|
|
||||||
using bts;
|
|
||||||
using bts.collections;
|
|
||||||
using bts.websocket_core;
|
|
||||||
|
|
||||||
using Bts.DataStreamExpressions;
|
|
||||||
using Bts.Websocket;
|
|
||||||
|
|
||||||
using mbts.utils;
|
|
||||||
|
|
||||||
using Spark.Core.Disposables;
|
|
||||||
|
|
||||||
using System.Text.Json;
|
|
||||||
using System.Text.Json.Nodes;
|
|
||||||
|
|
||||||
public class DataStreamDataSync(JsonSerializerOptions? serializerOpts = null) : JsonRpcDataSync(serializerOpts) {
|
|
||||||
readonly Dictionary<WebsocketClientID, List<ClientSubscription>> clients = [];
|
|
||||||
|
|
||||||
protected override void onConnect(WebsocketConnectNotification notification) {
|
|
||||||
var id = notification.Id;
|
|
||||||
lock (lockObj) {
|
|
||||||
clients.Add(id, []);
|
|
||||||
}
|
|
||||||
Log.Api.info($"DataStreamWebsocketDataSync client connected. clientId: {notification.Id}");
|
|
||||||
if (notification.Message is not null) {
|
|
||||||
onIncomingMessage(notification.Message);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override void onDisconnect(WebsocketDisconnectNotification notification) {
|
|
||||||
Log.Api.warn($"DataStreamWebsocketDataSync client disconnected. clientId: {notification.Id}");
|
|
||||||
unsubscribeAll(notification.Id);
|
|
||||||
clients.Remove(notification.Id);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override void onRequest(WebsocketClientID id, JsonRpcRequest<JsonObject?> req) {
|
|
||||||
switch (req.Method!.ToLower()) {
|
|
||||||
case "subscribe":
|
|
||||||
subscribe(id, req);
|
|
||||||
break;
|
|
||||||
case "unsubscribe":
|
|
||||||
unsubscribe(id, req);
|
|
||||||
break;
|
|
||||||
case "unsubscribe_all":
|
|
||||||
unsubscribeAll(id);
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
Log.Api.error($"Received request of unknown type: {req}");
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void subscribe(WebsocketClientID id, JsonRpcRequest<JsonObject?> req) {
|
|
||||||
var subParams = req.Params!.Deserialize<SubscriptionParams>()!;
|
|
||||||
var exp = DataStreamExpressionsInterpreter.Evaluate(subParams.Query!);
|
|
||||||
var stream = exp.getView(new Dictionary<string, StatementExpression>(0));
|
|
||||||
var sub = new ClientSubscription(id, subParams, Disposable.Create(Dispose));
|
|
||||||
lock (lockObj) {
|
|
||||||
clients[id].Add(sub);
|
|
||||||
sendResponse(id, req.Id!.Value, req.Params);
|
|
||||||
stream.onChange += OnChange;
|
|
||||||
}
|
|
||||||
|
|
||||||
void OnChange(object? newValue) {
|
|
||||||
var dp = ExceptionAssert.of_type<DataPacket>(newValue);
|
|
||||||
var rpcParams = new {
|
|
||||||
dp.addedRows,
|
|
||||||
dp.deletedRows
|
|
||||||
};
|
|
||||||
sendNotification(id, "subscription", rpcParams);
|
|
||||||
}
|
|
||||||
|
|
||||||
void Dispose() =>
|
|
||||||
stream.onChange -= OnChange;
|
|
||||||
}
|
|
||||||
|
|
||||||
void unsubscribe(WebsocketClientID id, JsonRpcRequest<JsonObject?> req) {
|
|
||||||
var subParams = req.Params!.Deserialize<SubscriptionParams>();
|
|
||||||
var subParamsStr = JsonSerializer.Serialize(subParams, serializerOpts);
|
|
||||||
// Minimize amount of code inside lock (just need it to find/remove the subscription)
|
|
||||||
List<ClientSubscription>? subs = null;
|
|
||||||
ClientSubscription? sub = null;
|
|
||||||
lock (lockObj) {
|
|
||||||
subs = clients.GetValueOrDefault(id);
|
|
||||||
subs?.TryRemoveFirst(s => s!.Params.Equals(subParams), out sub);
|
|
||||||
}
|
|
||||||
if (subs is null) {
|
|
||||||
// Didn't find client (log/throw).
|
|
||||||
var msg = $"Unable to unsubscribe: did not find client. Id: {id}.\nParams:\n{subParamsStr}";
|
|
||||||
Log.Api.error(msg);
|
|
||||||
throw new Exception(msg);
|
|
||||||
}
|
|
||||||
if (sub is null) {
|
|
||||||
// Found client, but didn't find subscription (log/throw).
|
|
||||||
var msg = $"Unable to unsubscribe: did not find the specified subscription. " +
|
|
||||||
$"Id: {id}. Request:\n{subParams}";
|
|
||||||
Log.Api.error(msg);
|
|
||||||
throw new Exception(msg);
|
|
||||||
}
|
|
||||||
// Found client and subscription (dispose/remove subscription).
|
|
||||||
Log.Api.info($"Unsubscribing client. Id: {id}.\nParams:\n{subParamsStr}");
|
|
||||||
sub.Dispose();
|
|
||||||
}
|
|
||||||
|
|
||||||
void unsubscribeAll(WebsocketClientID id) {
|
|
||||||
lock (lockObj) {
|
|
||||||
if (!clients.TryGetValue(id, out var subs)) {
|
|
||||||
throw new Exception(
|
|
||||||
$"Unable to unsubscribe: did not find client with id {id}");
|
|
||||||
}
|
|
||||||
subs.DisposeAll();
|
|
||||||
subs.Clear();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,228 +1,229 @@
|
|||||||
namespace Bts.DataStream.Websocket;
|
namespace Bts.DataStream.Websocket;
|
||||||
|
|
||||||
using BCG;
|
using BCG;
|
||||||
|
|
||||||
using bts;
|
using bts;
|
||||||
using bts.api.core;
|
using bts.api.core;
|
||||||
using bts.collections;
|
using bts.collections;
|
||||||
|
|
||||||
using Bts.DataStreamExpressions;
|
using Bts.DataStreamExpressions;
|
||||||
using Bts.Websocket;
|
using Bts.Websocket;
|
||||||
|
|
||||||
using mbts.utils;
|
using mbts.utils;
|
||||||
|
|
||||||
using Spark.Core.Disposables;
|
using Spark.Core.Disposables;
|
||||||
|
|
||||||
using System.Reactive.Subjects;
|
using System.Reactive.Subjects;
|
||||||
using System.Text.Json;
|
using System.Text.Json;
|
||||||
using System.Text.Json.Nodes;
|
using System.Text.Json.Nodes;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Represents an I/O model (<see cref="ISubject{TSource, TResult}"/>), that accepts websocket
|
/// Represents an I/O model (<see cref="ISubject{TSource, TResult}"/>), that accepts websocket
|
||||||
/// requests (from clients) as input, and emits data/notifications (to clients) as output.
|
/// requests (from clients) as input, and emits data/notifications (to clients) as output.
|
||||||
/// This class's chief role is managing data subscriptions to sparkd on the clients' behalf.
|
/// This class's chief role is managing data subscriptions to sparkd on the clients' behalf.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class DataSync(JsonSerializerOptions? serializerOpts = null) :
|
public class DataSync(ServiceContext serviceCtx, JsonSerializerOptions? serializerOpts = null) :
|
||||||
ISubject<WebsocketConnectionUpdate, IEnumerable<WebsocketOutgoingMessage>> {
|
ISubject<WebsocketConnectionUpdate, IEnumerable<WebsocketOutgoingMessage>> {
|
||||||
protected readonly object lockObj = new();
|
readonly ServiceContext serviceCtx = serviceCtx;
|
||||||
readonly JsonSerializerOptions serializerOpts = serializerOpts ?? JsonSerializerOptions.Default;
|
readonly JsonSerializerOptions serializerOpts = serializerOpts ?? JsonSerializerOptions.Default;
|
||||||
readonly Dictionary<WebsocketClientID, List<ClientSubscription>> clients = [];
|
readonly Dictionary<WebsocketClientID, List<ClientSubscription>> clients = [];
|
||||||
|
readonly object lockObj = new();
|
||||||
event Action<IEnumerable<WebsocketOutgoingMessage>>? requestSend;
|
|
||||||
|
event Action<IEnumerable<WebsocketOutgoingMessage>>? requestSend;
|
||||||
public void OnCompleted() =>
|
|
||||||
Log.Api.info("DataSync incoming request stream has completed.");
|
public void OnCompleted() =>
|
||||||
|
Log.Api.info("DataSync incoming request stream has completed.");
|
||||||
public void OnError(Exception error) =>
|
|
||||||
Log.Api.error($"DataSync error: {error}");
|
public void OnError(Exception error) =>
|
||||||
|
Log.Api.error($"DataSync error: {error}");
|
||||||
public void OnNext(WebsocketConnectionUpdate value) {
|
|
||||||
switch (value.Type) {
|
public void OnNext(WebsocketConnectionUpdate value) {
|
||||||
case WebsocketConnectionUpdateType.IncomingMessage:
|
switch (value.Type) {
|
||||||
onIncomingMessage(value.IncomingMessage);
|
case WebsocketConnectionUpdateType.IncomingMessage:
|
||||||
break;
|
onIncomingMessage(value.IncomingMessage);
|
||||||
case WebsocketConnectionUpdateType.Connect:
|
break;
|
||||||
onConnect(value.Connect);
|
case WebsocketConnectionUpdateType.Connect:
|
||||||
break;
|
onConnect(value.Connect);
|
||||||
case WebsocketConnectionUpdateType.Disconnect:
|
break;
|
||||||
onDisconnect(value.Disconnect);
|
case WebsocketConnectionUpdateType.Disconnect:
|
||||||
break;
|
onDisconnect(value.Disconnect);
|
||||||
default:
|
break;
|
||||||
Log.assert(false, $"Unexpected WebsocketConnectionUpdate type: {value.Type}");
|
default:
|
||||||
break;
|
Log.assert(false, $"Unexpected WebsocketConnectionUpdate type: {value.Type}");
|
||||||
}
|
break;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
public IDisposable Subscribe(IObserver<IEnumerable<WebsocketOutgoingMessage>> observer) {
|
|
||||||
lock (lockObj) {
|
public IDisposable Subscribe(IObserver<IEnumerable<WebsocketOutgoingMessage>> observer) {
|
||||||
requestSend += observer.OnNext;
|
lock (lockObj) {
|
||||||
return Disposable.Create(Dispose);
|
requestSend += observer.OnNext;
|
||||||
}
|
return Disposable.Create(dispose);
|
||||||
void Dispose() {
|
}
|
||||||
lock (lockObj) {
|
void dispose() {
|
||||||
requestSend -= observer.OnNext;
|
lock (lockObj) {
|
||||||
}
|
requestSend -= observer.OnNext;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
static JsonRpcId? extractRpcId(string messageBody) {
|
|
||||||
try {
|
static JsonRpcId? extractRpcId(string messageBody) {
|
||||||
return JsonNode.Parse(messageBody)
|
try {
|
||||||
?.AsObject()
|
return JsonNode.Parse(messageBody)
|
||||||
?.GetValueOrDefault("id")
|
?.AsObject()
|
||||||
?.Deserialize<JsonRpcId>();
|
?.GetValueOrDefault("id")
|
||||||
} catch {
|
?.Deserialize<JsonRpcId>();
|
||||||
return null;
|
} catch {
|
||||||
}
|
return null;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
void onConnect(WebsocketConnectNotification notification) {
|
|
||||||
var id = notification.Id;
|
void onConnect(WebsocketConnectNotification notification) {
|
||||||
lock (lockObj) {
|
var id = notification.Id;
|
||||||
clients.Add(id, []);
|
lock (lockObj) {
|
||||||
}
|
clients.Add(id, new());
|
||||||
Log.Api.info($"DataStreamWebsocketDataSync client connected. clientId: {notification.Id}");
|
}
|
||||||
if (notification.Message is not null) {
|
Log.Api.info($"DataStreamWebsocketDataSync client connected. clientId: {notification.Id}");
|
||||||
onIncomingMessage(notification.Message);
|
if (notification.Message is not null) {
|
||||||
}
|
onIncomingMessage(notification.Message);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
void onDisconnect(WebsocketDisconnectNotification notification) {
|
|
||||||
Log.Api.warn($"DataStreamWebsocketDataSync client disconnected. clientId: {notification.Id}");
|
void onDisconnect(WebsocketDisconnectNotification notification) {
|
||||||
unsubscribeAll(notification.Id);
|
Log.Api.warn($"DataStreamWebsocketDataSync client disconnected. clientId: {notification.Id}");
|
||||||
clients.Remove(notification.Id);
|
unsubscribeAll(notification.Id);
|
||||||
}
|
clients.Remove(notification.Id);
|
||||||
|
}
|
||||||
void onIncomingMessage(WebsocketIncomingMessage message) {
|
|
||||||
var id = message.sender_client_id;
|
void onIncomingMessage(WebsocketIncomingMessage message) {
|
||||||
if (!JsonRpcRequest.TryDeserialize(message, out var req, out var err)) {
|
var id = message.sender_client_id;
|
||||||
onBadRequest(message, err);
|
if (!JsonRpcRequest.TryDeserialize(message, out var req, out var err)) {
|
||||||
return;
|
onBadRequest(message, err);
|
||||||
}
|
return;
|
||||||
switch (req.Method!.ToLower()) {
|
}
|
||||||
case "subscribe":
|
switch (req.Method!.ToLower()) {
|
||||||
subscribe(id, req);
|
case "subscribe":
|
||||||
break;
|
subscribe(id, req);
|
||||||
case "unsubscribe":
|
break;
|
||||||
unsubscribe(id, req);
|
case "unsubscribe":
|
||||||
break;
|
unsubscribe(id, req);
|
||||||
case "unsubscribe_all":
|
break;
|
||||||
unsubscribeAll(id);
|
case "unsubscribe_all":
|
||||||
break;
|
unsubscribeAll(id);
|
||||||
default:
|
break;
|
||||||
Log.Api.error($"Received request of unknown type: {req}");
|
default:
|
||||||
break;
|
Log.Api.error($"Received request of unknown type: {req}");
|
||||||
}
|
break;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
void sendNotification<T>(WebsocketClientID id, string method, T rpcParams) {
|
|
||||||
var rpcNotification = JsonRpcRequest.CreateNotification(method, rpcParams);
|
void sendNotification<T>(WebsocketClientID id, string method, T rpcParams) {
|
||||||
var rpcNotificationStr = JsonSerializer.Serialize(rpcNotification, serializerOpts);
|
var rpcNotification = JsonRpcRequest.CreateNotification(method, rpcParams);
|
||||||
send([new(id, rpcNotificationStr)]);
|
var rpcNotificationStr = JsonSerializer.Serialize(rpcNotification, serializerOpts);
|
||||||
}
|
send(new WebsocketOutgoingMessage[] { new(id, rpcNotificationStr) });
|
||||||
|
}
|
||||||
void sendResponse<T>(WebsocketClientID id, JsonRpcId rpcId, T rpcParams) {
|
|
||||||
var rpcRes = JsonRpcResponse.Create(rpcId, rpcParams);
|
void sendResponse<T>(WebsocketClientID id, JsonRpcId rpcId, T rpcParams) {
|
||||||
var rpcResStr = JsonSerializer.Serialize(rpcRes, serializerOpts);
|
var rpcRes = JsonRpcResponse.Create(rpcId, rpcParams);
|
||||||
send([new(id, rpcResStr )]);
|
var rpcResStr = JsonSerializer.Serialize(rpcRes, serializerOpts);
|
||||||
}
|
send(new WebsocketOutgoingMessage[] { new(id, rpcResStr )});
|
||||||
|
}
|
||||||
void sendError<T>(WebsocketClientID id, JsonRpcId rpcId, JsonRpcError<T> error) {
|
|
||||||
var rpcRes = JsonRpcResponse.CreateError(rpcId, error);
|
void sendError<T>(WebsocketClientID id, JsonRpcId rpcId, JsonRpcError<T> error) {
|
||||||
var rpcResStr = JsonSerializer.Serialize(rpcRes, serializerOpts);
|
var rpcRes = JsonRpcResponse.CreateError(rpcId, error);
|
||||||
send([new(id, rpcResStr)]);
|
var rpcResStr = JsonSerializer.Serialize(rpcRes, serializerOpts);
|
||||||
}
|
send(new WebsocketOutgoingMessage[] { new(id, rpcResStr) });
|
||||||
|
}
|
||||||
bool send(IEnumerable<WebsocketOutgoingMessage> messages) {
|
|
||||||
lock (lockObj) {
|
bool send(IEnumerable<WebsocketOutgoingMessage> messages) {
|
||||||
if (requestSend is null) {
|
lock (lockObj) {
|
||||||
Log.Api.warn(
|
if (requestSend is null) {
|
||||||
$"Unable to send message(s) because '{nameof(requestSend)}' handler is null");
|
Log.Api.warn(
|
||||||
return false;
|
$"Unable to send message(s) because '{nameof(requestSend)}' handler is null");
|
||||||
}
|
return false;
|
||||||
requestSend(messages);
|
}
|
||||||
return true;
|
requestSend(messages);
|
||||||
}
|
return true;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
void subscribe(WebsocketClientID id, JsonRpcRequest<JsonObject?> req) {
|
|
||||||
var subParams = req.Params!.Deserialize<SubscriptionParams>()!;
|
void subscribe(WebsocketClientID id, JsonRpcRequest<JsonObject?> req) {
|
||||||
var exp = DataStreamExpressionsInterpreter.Evaluate(subParams.Query!);
|
var subParams = req.Params!.Deserialize<SubscriptionParams>()!;
|
||||||
var stream = exp.getView(new Dictionary<string, StatementExpression>(0));
|
var exp = DataStreamExpressionsInterpreter.Evaluate(subParams.Query!);
|
||||||
var sub = new ClientSubscription(id, subParams, Disposable.Create(Dispose));
|
var stream = exp.getView(new Dictionary<string, StatementExpression>(0));
|
||||||
lock (lockObj) {
|
var sub = new ClientSubscription(id, subParams, Disposable.Create(dispose));
|
||||||
clients[id].Add(sub);
|
lock (lockObj) {
|
||||||
sendResponse(id, req.Id!.Value, req.Params);
|
clients[id].Add(sub);
|
||||||
stream.onChange += OnChange;
|
sendResponse(id, req.Id!.Value, req.Params);
|
||||||
}
|
stream.onChange += onChange;
|
||||||
|
}
|
||||||
void OnChange(object? newValue) {
|
|
||||||
var dp = ExceptionAssert.of_type<DataPacket>(newValue);
|
void onChange(object? newValue) {
|
||||||
var rpcParams = new {
|
var dp = ExceptionAssert.of_type<DataPacket>(newValue);
|
||||||
dp.addedRows,
|
var rpcParams = new {
|
||||||
dp.deletedRows
|
addedRows = dp.addedRows,
|
||||||
};
|
deletedRows = dp.deletedRows
|
||||||
sendNotification(id, "subscription", rpcParams);
|
};
|
||||||
}
|
sendNotification(id, "subscription", rpcParams);
|
||||||
|
}
|
||||||
void Dispose() =>
|
|
||||||
stream.onChange -= OnChange;
|
void dispose() =>
|
||||||
}
|
stream.onChange -= onChange;
|
||||||
|
}
|
||||||
void unsubscribe(WebsocketClientID id, JsonRpcRequest<JsonObject?> req) {
|
|
||||||
var subParams = req.Params!.Deserialize<SubscriptionParams>();
|
void unsubscribe(WebsocketClientID id, JsonRpcRequest<JsonObject?> req) {
|
||||||
var subParamsStr = JsonSerializer.Serialize(subParams, serializerOpts);
|
var subParams = req.Params!.Deserialize<SubscriptionParams>();
|
||||||
// Minimize amount of code inside lock (just need it to find/remove the subscription)
|
var subParamsStr = JsonSerializer.Serialize(subParams, serializerOpts);
|
||||||
List<ClientSubscription>? subs = null;
|
// Minimize amount of code inside lock (just need it to find/remove the subscription)
|
||||||
ClientSubscription? sub = null;
|
List<ClientSubscription>? subs = null;
|
||||||
lock (lockObj) {
|
ClientSubscription? sub = null;
|
||||||
subs = clients.GetValueOrDefault(id);
|
lock (lockObj) {
|
||||||
subs?.TryRemoveFirst(s => s!.Params.Equals(subParams), out sub);
|
subs = clients.GetValueOrDefault(id);
|
||||||
}
|
subs?.TryRemoveFirst(s => s!.Params.Equals(subParams), out sub);
|
||||||
if (subs is null) {
|
}
|
||||||
// Didn't find client (log/throw).
|
if (subs is null) {
|
||||||
var msg = $"Unable to unsubscribe: did not find client. Id: {id}.\nParams:\n{subParamsStr}";
|
// Didn't find client (log/throw).
|
||||||
Log.Api.error(msg);
|
var msg = $"Unable to unsubscribe: did not find client. Id: {id}.\nParams:\n{subParamsStr}";
|
||||||
throw new Exception(msg);
|
Log.Api.error(msg);
|
||||||
}
|
throw new Exception(msg);
|
||||||
if (sub is null) {
|
}
|
||||||
// Found client, but didn't find subscription (log/throw).
|
if (sub is null) {
|
||||||
var msg = $"Unable to unsubscribe: did not find the specified subscription. " +
|
// Found client, but didn't find subscription (log/throw).
|
||||||
$"Id: {id}. Request:\n{subParams}";
|
var msg = $"Unable to unsubscribe: did not find the specified subscription. " +
|
||||||
Log.Api.error(msg);
|
$"Id: {id}. Request:\n{subParams}";
|
||||||
throw new Exception(msg);
|
Log.Api.error(msg);
|
||||||
}
|
throw new Exception(msg);
|
||||||
// Found client and subscription (dispose/remove subscription).
|
}
|
||||||
Log.Api.info($"Unsubscribing client. Id: {id}.\nParams:\n{subParamsStr}");
|
// Found client and subscription (dispose/remove subscription).
|
||||||
sub.Dispose();
|
Log.Api.info($"Unsubscribing client. Id: {id}.\nParams:\n{subParamsStr}");
|
||||||
}
|
sub.Dispose();
|
||||||
|
}
|
||||||
void unsubscribeAll(WebsocketClientID id) {
|
|
||||||
lock (lockObj) {
|
void unsubscribeAll(WebsocketClientID id) {
|
||||||
if (!clients.TryGetValue(id, out var subs)) {
|
lock (lockObj) {
|
||||||
throw new Exception(
|
if (!clients.TryGetValue(id, out var subs)) {
|
||||||
$"Unable to unsubscribe: did not find client with id {id}");
|
throw new Exception(
|
||||||
}
|
$"Unable to unsubscribe: did not find client with id {id}");
|
||||||
subs.DisposeAll();
|
}
|
||||||
subs.Clear();
|
subs.DisposeAll();
|
||||||
}
|
subs.Clear();
|
||||||
}
|
}
|
||||||
|
}
|
||||||
void onBadRequest(WebsocketIncomingMessage req, string? errorMsg) {
|
|
||||||
var rpcId = extractRpcId(req.message_body);
|
void onBadRequest(WebsocketIncomingMessage req, string? errorMsg) {
|
||||||
var logMsg = $"Recieved invalid request:\n{req.message_body}";
|
var rpcId = extractRpcId(req.message_body);
|
||||||
if (errorMsg is not null) {
|
var logMsg = $"Recieved invalid request:\n{req.message_body}";
|
||||||
logMsg += $"\n\n**Exception**\n{errorMsg}";
|
if (errorMsg is not null) {
|
||||||
}
|
logMsg += $"\n\n**Exception**\n{errorMsg}";
|
||||||
Log.Api.error(logMsg);
|
}
|
||||||
if (rpcId is null) {
|
Log.Api.error(logMsg);
|
||||||
return;
|
if (rpcId is null) {
|
||||||
}
|
return;
|
||||||
// Send error back to client
|
}
|
||||||
var rpcErr = JsonRpcError.Create(
|
// Send error back to client
|
||||||
(long)JsonRpcErrorCode.InvalidRequest,
|
var rpcErr = JsonRpcError.Create(
|
||||||
$"Received invalid request: {errorMsg ?? "unknown error"}",
|
(long)JsonRpcErrorCode.InvalidRequest,
|
||||||
req.message_body);
|
$"Received invalid request: {errorMsg ?? "unknown error"}",
|
||||||
sendError(req.sender_client_id, rpcId.Value, rpcErr);
|
req.message_body);
|
||||||
}
|
sendError(req.sender_client_id, rpcId.Value, rpcErr);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
File renamed without changes.
@@ -1,48 +1,48 @@
|
|||||||
namespace Bts.DataStream.Websocket;
|
namespace Bts.DataStream.Websocket;
|
||||||
|
|
||||||
using Spark.Core;
|
using Spark.Core;
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.Text.Json;
|
using System.Text.Json;
|
||||||
using System.Text.Json.Serialization;
|
using System.Text.Json.Serialization;
|
||||||
|
|
||||||
class MultilineStringJsonConverter : JsonConverter<string> {
|
class MultilineStringJsonConverter : JsonConverter<string> {
|
||||||
public override string? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) {
|
public override string? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) {
|
||||||
if (typeToConvert != typeof(string)) {
|
if (typeToConvert != typeof(string)) {
|
||||||
throw new ArgumentException($"Unable to parse {typeToConvert}", nameof(typeToConvert));
|
throw new ArgumentException($"Unable to parse {typeToConvert}", nameof(typeToConvert));
|
||||||
}
|
}
|
||||||
return reader.TokenType switch {
|
return reader.TokenType switch {
|
||||||
JsonTokenType.String => StringImpl(ref reader),
|
JsonTokenType.String => stringImpl(ref reader),
|
||||||
JsonTokenType.StartArray => ArrayImpl(ref reader),
|
JsonTokenType.StartArray => arrayImpl(ref reader),
|
||||||
_ => throw new ArgumentException(
|
_ => throw new ArgumentException(
|
||||||
$"Unable to parse {typeof(SubscriptionParams)} from {reader.TokenType}"),
|
$"Unable to parse {typeof(SubscriptionParams)} from {reader.TokenType}"),
|
||||||
};
|
};
|
||||||
|
|
||||||
static string? StringImpl(ref Utf8JsonReader reader) =>
|
static string? stringImpl(ref Utf8JsonReader reader) =>
|
||||||
new(reader.GetString()!);
|
new(reader.GetString()!);
|
||||||
|
|
||||||
static string? ArrayImpl(ref Utf8JsonReader reader) {
|
static string? arrayImpl(ref Utf8JsonReader reader) {
|
||||||
var lines = new List<string>(2);
|
var lines = new List<string>(2);
|
||||||
while (reader.Read()) {
|
while (reader.Read()) {
|
||||||
switch (reader.TokenType) {
|
switch (reader.TokenType) {
|
||||||
case JsonTokenType.String:
|
case JsonTokenType.String:
|
||||||
lines.Add(reader.GetString()!);
|
lines.Add(reader.GetString()!);
|
||||||
break;
|
break;
|
||||||
case JsonTokenType.EndArray:
|
case JsonTokenType.EndArray:
|
||||||
return string.Join("\n", lines);
|
return string.Join("\n", lines);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
throw new JsonException(
|
throw new JsonException(
|
||||||
$"Failed to parse {typeof(SubscriptionParams)} (did not reach end of array)");
|
$"Failed to parse {typeof(SubscriptionParams)} (did not reach end of array)");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public override void Write(Utf8JsonWriter writer, string value, JsonSerializerOptions options) {
|
public override void Write(Utf8JsonWriter writer, string value, JsonSerializerOptions options) {
|
||||||
var lines = value.Split("\n");
|
var lines = value.Split("\n");
|
||||||
if (lines.Length < 2) {
|
if (lines.Length < 2) {
|
||||||
writer.WriteRawValue(value);
|
writer.WriteRawValue(value);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
writer.WriteRawValue($"[{", ".join(lines)}]");
|
writer.WriteRawValue($"[{", ".join(lines)}]");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,31 +1,27 @@
|
|||||||
namespace Bts.DataStream.Websocket;
|
namespace Bts.DataStream.Websocket;
|
||||||
|
|
||||||
using bts;
|
using bts;
|
||||||
using bts.api.core;
|
using bts.api.core;
|
||||||
|
|
||||||
using Bts.Websocket;
|
using Bts.Websocket;
|
||||||
|
|
||||||
using Google.Protobuf;
|
using Google.Protobuf;
|
||||||
|
|
||||||
using Spark.Core.Disposables;
|
using System.Text.Json;
|
||||||
|
|
||||||
|
public static class Session {
|
||||||
using System.Text.Json;
|
static int sessionIdCounter;
|
||||||
|
|
||||||
public static class Session {
|
public static async Task Start(
|
||||||
static int sessionIdCounter;
|
ServiceContext context, WebsocketServerConfig config, JsonSerializerOptions? opts) {
|
||||||
|
opts ??= JsonSerializerOptions.Default;
|
||||||
public static async Task Start(
|
var sessionId = Interlocked.Increment(ref sessionIdCounter);
|
||||||
ServiceContext context, WebsocketServerConfig config, JsonSerializerOptions? opts) {
|
var gateway = new WebsocketGatewayObservable(config);
|
||||||
opts ??= JsonSerializerOptions.Default;
|
var sync = new DataSync(context, opts);
|
||||||
var sessionId = Interlocked.Increment(ref sessionIdCounter);
|
using var syncSub = gateway.Subscribe(sync);
|
||||||
var gateway = new WebsocketGatewayObservable(config);
|
using var gwSub = sync.Subscribe(gateway);
|
||||||
var sync = new DataSync(context, opts);
|
Log.Api.info($"Starting DataStream Websocket Session {sessionId}");
|
||||||
using (gateway.Subscribe(sync))
|
Log.Api.info($"DataStream Websocket Session {sessionId} config:\n{JsonFormatter.Default.Format(config)}");
|
||||||
using (sync.Subscribe(gateway)) {
|
await gateway.ConnectAsync();
|
||||||
Log.Api.info($"Starting DataStream Websocket Session {sessionId}");
|
}
|
||||||
Log.Api.info($"DataStream Websocket Session {sessionId} config:\n{JsonFormatter.Default.Format(config)}");
|
}
|
||||||
await gateway.ConnectAsync();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,110 +1,110 @@
|
|||||||
namespace Bts.DataStream.Websocket;
|
namespace Bts.DataStream.Websocket;
|
||||||
|
|
||||||
using Spark.Core;
|
using Spark.Core;
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.Diagnostics.CodeAnalysis;
|
using System.Diagnostics.CodeAnalysis;
|
||||||
using System.Text.Json;
|
using System.Text.Json;
|
||||||
using System.Text.Json.Serialization;
|
using System.Text.Json.Serialization;
|
||||||
|
|
||||||
public class SubscriptionParams : IEquatable<SubscriptionParams> {
|
public class SubscriptionParams : IEquatable<SubscriptionParams> {
|
||||||
static class PropNames {
|
static class PropNames {
|
||||||
public const string QUERY = "query";
|
public const string QUERY = "query";
|
||||||
}
|
}
|
||||||
|
|
||||||
public SubscriptionParams(string? query) {
|
public SubscriptionParams(string? query) {
|
||||||
Query = query;
|
Query = query;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Serializer supports plain string, or an array of lines for convenience
|
// Serializer supports plain string, or an array of lines for convenience
|
||||||
[MaybeNull]
|
[MaybeNull]
|
||||||
[JsonPropertyName(PropNames.QUERY)]
|
[JsonPropertyName(PropNames.QUERY)]
|
||||||
[JsonConverter(typeof(MultilineStringJsonConverter))]
|
[JsonConverter(typeof(MultilineStringJsonConverter))]
|
||||||
public string? Query { get; set; }
|
public string? Query { get; set; }
|
||||||
|
|
||||||
public bool Equals(SubscriptionParams? other) =>
|
public bool Equals(SubscriptionParams? other) =>
|
||||||
other is not null &&
|
other is not null &&
|
||||||
Query == other.Query;
|
Query == other.Query;
|
||||||
|
|
||||||
class SubscriptionParamsConverter : JsonConverter<SubscriptionParams> {
|
class SubscriptionParamsConverter : JsonConverter<SubscriptionParams> {
|
||||||
public override bool CanConvert(Type typeToConvert) =>
|
public override bool CanConvert(Type typeToConvert) =>
|
||||||
typeToConvert == typeof(SubscriptionParams);
|
typeToConvert == typeof(SubscriptionParams);
|
||||||
|
|
||||||
public override SubscriptionParams Read(
|
public override SubscriptionParams Read(
|
||||||
ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) {
|
ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) {
|
||||||
if (typeToConvert != typeof(SubscriptionParams)) {
|
if (typeToConvert != typeof(SubscriptionParams)) {
|
||||||
throw new ArgumentException($"Unable to parse {typeToConvert}", nameof(typeToConvert));
|
throw new ArgumentException($"Unable to parse {typeToConvert}", nameof(typeToConvert));
|
||||||
}
|
}
|
||||||
return read(ref reader);
|
return read(ref reader);
|
||||||
}
|
}
|
||||||
|
|
||||||
SubscriptionParams read(ref Utf8JsonReader reader) {
|
SubscriptionParams read(ref Utf8JsonReader reader) {
|
||||||
return reader.TokenType switch {
|
return reader.TokenType switch {
|
||||||
JsonTokenType.String => stringImpl(ref reader),
|
JsonTokenType.String => stringImpl(ref reader),
|
||||||
JsonTokenType.StartArray => arrayImpl(ref reader),
|
JsonTokenType.StartArray => arrayImpl(ref reader),
|
||||||
JsonTokenType.StartObject => objectImpl(ref reader),
|
JsonTokenType.StartObject => objectImpl(ref reader),
|
||||||
_ => throw new ArgumentException(
|
_ => throw new ArgumentException(
|
||||||
$"Unable to parse {typeof(SubscriptionParams)} from {reader.TokenType}"),
|
$"Unable to parse {typeof(SubscriptionParams)} from {reader.TokenType}"),
|
||||||
};
|
};
|
||||||
|
|
||||||
static SubscriptionParams stringImpl(ref Utf8JsonReader reader) =>
|
static SubscriptionParams stringImpl(ref Utf8JsonReader reader) =>
|
||||||
new(reader.GetString()!);
|
new(reader.GetString()!);
|
||||||
|
|
||||||
static SubscriptionParams arrayImpl(ref Utf8JsonReader reader) {
|
static SubscriptionParams arrayImpl(ref Utf8JsonReader reader) {
|
||||||
var lines = new List<string>(2);
|
var lines = new List<string>(2);
|
||||||
while (reader.Read()) {
|
while (reader.Read()) {
|
||||||
switch (reader.TokenType) {
|
switch (reader.TokenType) {
|
||||||
case JsonTokenType.String:
|
case JsonTokenType.String:
|
||||||
lines.Add(reader.GetString()!);
|
lines.Add(reader.GetString()!);
|
||||||
break;
|
break;
|
||||||
case JsonTokenType.EndArray:
|
case JsonTokenType.EndArray:
|
||||||
return new("\n".join(lines));
|
return new("\n".join(lines));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
throw new JsonException(
|
throw new JsonException(
|
||||||
$"Failed to parse {typeof(SubscriptionParams)} (did not reach end of array)");
|
$"Failed to parse {typeof(SubscriptionParams)} (did not reach end of array)");
|
||||||
}
|
}
|
||||||
|
|
||||||
static SubscriptionParams objectImpl(ref Utf8JsonReader reader) {
|
static SubscriptionParams objectImpl(ref Utf8JsonReader reader) {
|
||||||
var lines = new List<string>(2);
|
var lines = new List<string>(2);
|
||||||
while (reader.Read()) {
|
while (reader.Read()) {
|
||||||
switch (reader.TokenType) {
|
switch (reader.TokenType) {
|
||||||
case JsonTokenType.PropertyName:
|
case JsonTokenType.PropertyName:
|
||||||
var propName = reader.GetString()!;
|
var propName = reader.GetString()!;
|
||||||
if (propName == PropNames.QUERY) {
|
if (propName == PropNames.QUERY) {
|
||||||
switch (reader.TokenType) {
|
switch (reader.TokenType) {
|
||||||
case JsonTokenType.StartArray:
|
case JsonTokenType.StartArray:
|
||||||
return arrayImpl(ref reader);
|
return arrayImpl(ref reader);
|
||||||
case JsonTokenType.String:
|
case JsonTokenType.String:
|
||||||
return stringImpl(ref reader);
|
return stringImpl(ref reader);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case JsonTokenType.String:
|
case JsonTokenType.String:
|
||||||
lines.Add(reader.GetString()!);
|
lines.Add(reader.GetString()!);
|
||||||
break;
|
break;
|
||||||
case JsonTokenType.EndArray:
|
case JsonTokenType.EndArray:
|
||||||
return new("\n".join(lines));
|
return new("\n".join(lines));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
throw new JsonException(
|
throw new JsonException(
|
||||||
$"Failed to parse {typeof(SubscriptionParams)} (did not reach end of array)");
|
$"Failed to parse {typeof(SubscriptionParams)} (did not reach end of array)");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public override void Write(
|
public override void Write(
|
||||||
Utf8JsonWriter writer, SubscriptionParams value, JsonSerializerOptions options) {
|
Utf8JsonWriter writer, SubscriptionParams value, JsonSerializerOptions options) {
|
||||||
var q = value.Query;
|
var q = value.Query;
|
||||||
var lines = q?.Split("\n");
|
var lines = q?.Split("\n");
|
||||||
if (lines is null || lines.Length < 2) {
|
if (lines is null || lines.Length < 2) {
|
||||||
writer.WriteString(PropNames.QUERY, value.Query);
|
writer.WriteString(PropNames.QUERY, value.Query);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
writer.WriteStartArray(PropNames.QUERY);
|
writer.WriteStartArray(PropNames.QUERY);
|
||||||
foreach (var l in lines) {
|
foreach (var l in lines) {
|
||||||
writer.WriteStringValue(l);
|
writer.WriteStringValue(l);
|
||||||
}
|
}
|
||||||
writer.WriteEndArray();
|
writer.WriteEndArray();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,41 +0,0 @@
|
|||||||
#nullable enable
|
|
||||||
using BCG;
|
|
||||||
|
|
||||||
using Spark.Core.Json;
|
|
||||||
|
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Text.Json;
|
|
||||||
using System.Text.Json.Serialization;
|
|
||||||
|
|
||||||
namespace Bts.DataStreamCore;
|
|
||||||
|
|
||||||
class ViewStreamUpdate {
|
|
||||||
[JsonPropertyName("tableName")]
|
|
||||||
public string? TableName { get; set; }
|
|
||||||
|
|
||||||
[JsonPropertyName("columns")]
|
|
||||||
public List<ColumnInfo>? Columns { get; set; }
|
|
||||||
|
|
||||||
[JsonPropertyName("addedRows")]
|
|
||||||
public List<Dictionary<string, object?>>? AddedRows { get; set; }
|
|
||||||
|
|
||||||
[JsonPropertyName("deletedRows")]
|
|
||||||
public List<Dictionary<string, object?>>? DeletedRows { get; set; }
|
|
||||||
|
|
||||||
public DataPacket ToDataPacket(BlueTable state, bool applyChanges = true) {
|
|
||||||
var schema = new BlueTableSchema([.. Columns!], TableName);
|
|
||||||
var addedRowsTable = new BlueTable(schema);
|
|
||||||
var deletedRowsTable = new BlueTable(schema);
|
|
||||||
if (applyChanges) {
|
|
||||||
state.deleteRows(deletedRowsTable);
|
|
||||||
state.addRows(addedRowsTable);
|
|
||||||
}
|
|
||||||
return new(state, addedRowsTable, deletedRowsTable);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static string ToJson() {
|
|
||||||
return "";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
+1
-1
@@ -1,7 +1,7 @@
|
|||||||
<Project Sdk="Microsoft.NET.Sdk.Web">
|
<Project Sdk="Microsoft.NET.Sdk.Web">
|
||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<TargetFramework>net8.0</TargetFramework>
|
<TargetFramework>net7.0</TargetFramework>
|
||||||
<Nullable>enable</Nullable>
|
<Nullable>enable</Nullable>
|
||||||
<ImplicitUsings>enable</ImplicitUsings>
|
<ImplicitUsings>enable</ImplicitUsings>
|
||||||
<EnableDefaultContentItems>True</EnableDefaultContentItems>
|
<EnableDefaultContentItems>True</EnableDefaultContentItems>
|
||||||
|
|||||||
+1
-1
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<OutputType>Exe</OutputType>
|
<OutputType>Exe</OutputType>
|
||||||
<TargetFramework>net8.0</TargetFramework>
|
<TargetFramework>net7.0</TargetFramework>
|
||||||
<ImplicitUsings>enable</ImplicitUsings>
|
<ImplicitUsings>enable</ImplicitUsings>
|
||||||
<Nullable>enable</Nullable>
|
<Nullable>enable</Nullable>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|||||||
@@ -1,20 +0,0 @@
|
|||||||
module.exports = {
|
|
||||||
root: true,
|
|
||||||
env: { browser: true, es2020: true },
|
|
||||||
extends: [
|
|
||||||
'eslint:recommended',
|
|
||||||
'plugin:react/recommended',
|
|
||||||
'plugin:react/jsx-runtime',
|
|
||||||
'plugin:react-hooks/recommended',
|
|
||||||
],
|
|
||||||
ignorePatterns: ['dist', '.eslintrc.cjs'],
|
|
||||||
parserOptions: { ecmaVersion: 'latest', sourceType: 'module' },
|
|
||||||
settings: { react: { version: '18.2' } },
|
|
||||||
plugins: ['react-refresh'],
|
|
||||||
rules: {
|
|
||||||
'react-refresh/only-export-components': [
|
|
||||||
'warn',
|
|
||||||
{ allowConstantExport: true },
|
|
||||||
],
|
|
||||||
},
|
|
||||||
}
|
|
||||||
@@ -1,24 +0,0 @@
|
|||||||
# Logs
|
|
||||||
logs
|
|
||||||
*.log
|
|
||||||
npm-debug.log*
|
|
||||||
yarn-debug.log*
|
|
||||||
yarn-error.log*
|
|
||||||
pnpm-debug.log*
|
|
||||||
lerna-debug.log*
|
|
||||||
|
|
||||||
node_modules
|
|
||||||
dist
|
|
||||||
dist-ssr
|
|
||||||
*.local
|
|
||||||
|
|
||||||
# Editor directories and files
|
|
||||||
.vscode/*
|
|
||||||
!.vscode/extensions.json
|
|
||||||
.idea
|
|
||||||
.DS_Store
|
|
||||||
*.suo
|
|
||||||
*.ntvs*
|
|
||||||
*.njsproj
|
|
||||||
*.sln
|
|
||||||
*.sw?
|
|
||||||
@@ -1,11 +0,0 @@
|
|||||||
<Project Sdk="Microsoft.VisualStudio.JavaScript.Sdk/0.5.128-alpha">
|
|
||||||
<PropertyGroup>
|
|
||||||
<StartupCommand>npm run dev</StartupCommand>
|
|
||||||
<JavaScriptTestRoot>src\</JavaScriptTestRoot>
|
|
||||||
<JavaScriptTestFramework>Jest</JavaScriptTestFramework>
|
|
||||||
<!-- Allows the build (or compile) script located on package.json to run on Build -->
|
|
||||||
<ShouldRunBuildScript>false</ShouldRunBuildScript>
|
|
||||||
<!-- Folder where production build objects will be placed -->
|
|
||||||
<PublishAssetsDirectory>$(MSBuildProjectDirectory)\dist</PublishAssetsDirectory>
|
|
||||||
</PropertyGroup>
|
|
||||||
</Project>
|
|
||||||
@@ -1,8 +0,0 @@
|
|||||||
# React + Vite
|
|
||||||
|
|
||||||
This template provides a minimal setup to get React working in Vite with HMR and some ESLint rules.
|
|
||||||
|
|
||||||
Currently, two official plugins are available:
|
|
||||||
|
|
||||||
- [@vitejs/plugin-react](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react/README.md) uses [Babel](https://babeljs.io/) for Fast Refresh
|
|
||||||
- [@vitejs/plugin-react-swc](https://github.com/vitejs/vite-plugin-react-swc) uses [SWC](https://swc.rs/) for Fast Refresh
|
|
||||||
@@ -1,13 +0,0 @@
|
|||||||
<!doctype html>
|
|
||||||
<html lang="en">
|
|
||||||
<head>
|
|
||||||
<meta charset="UTF-8" />
|
|
||||||
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
||||||
<title>Vite + React</title>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<div id="root"></div>
|
|
||||||
<script type="module" src="/src/main.jsx"></script>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
@@ -1,10 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<configuration>
|
|
||||||
<packageSources>
|
|
||||||
<clear />
|
|
||||||
<add key="nuget.org" value="https://api.nuget.org/v3/index.json" />
|
|
||||||
</packageSources>
|
|
||||||
<disabledPackageSources>
|
|
||||||
<clear />
|
|
||||||
</disabledPackageSources>
|
|
||||||
</configuration>
|
|
||||||
File diff suppressed because it is too large.
Load diff
@@ -1,27 +0,0 @@
|
|||||||
{
|
|
||||||
"name": "bts.datastreamwebapp.client",
|
|
||||||
"private": true,
|
|
||||||
"version": "0.0.0",
|
|
||||||
"type": "module",
|
|
||||||
"scripts": {
|
|
||||||
"dev": "vite",
|
|
||||||
"build": "vite build",
|
|
||||||
"lint": "eslint . --ext js,jsx --report-unused-disable-directives --max-warnings 0",
|
|
||||||
"preview": "vite preview"
|
|
||||||
},
|
|
||||||
"dependencies": {
|
|
||||||
"react": "^18.2.0",
|
|
||||||
"react-dom": "^18.2.0",
|
|
||||||
"react-use-websocket": "^4.5.0"
|
|
||||||
},
|
|
||||||
"devDependencies": {
|
|
||||||
"@types/react": "^18.2.43",
|
|
||||||
"@types/react-dom": "^18.2.17",
|
|
||||||
"@vitejs/plugin-react": "^4.2.1",
|
|
||||||
"eslint": "^8.55.0",
|
|
||||||
"eslint-plugin-react": "^7.33.2",
|
|
||||||
"eslint-plugin-react-hooks": "^4.6.0",
|
|
||||||
"eslint-plugin-react-refresh": "^0.4.5",
|
|
||||||
"vite": "^5.0.8"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--logos" width="31.88" height="32" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 257"><defs><linearGradient id="IconifyId1813088fe1fbc01fb466" x1="-.828%" x2="57.636%" y1="7.652%" y2="78.411%"><stop offset="0%" stop-color="#41D1FF"></stop><stop offset="100%" stop-color="#BD34FE"></stop></linearGradient><linearGradient id="IconifyId1813088fe1fbc01fb467" x1="43.376%" x2="50.316%" y1="2.242%" y2="89.03%"><stop offset="0%" stop-color="#FFEA83"></stop><stop offset="8.333%" stop-color="#FFDD35"></stop><stop offset="100%" stop-color="#FFA800"></stop></linearGradient></defs><path fill="url(#IconifyId1813088fe1fbc01fb466)" d="M255.153 37.938L134.897 252.976c-2.483 4.44-8.862 4.466-11.382.048L.875 37.958c-2.746-4.814 1.371-10.646 6.827-9.67l120.385 21.517a6.537 6.537 0 0 0 2.322-.004l117.867-21.483c5.438-.991 9.574 4.796 6.877 9.62Z"></path><path fill="url(#IconifyId1813088fe1fbc01fb467)" d="M185.432.063L96.44 17.501a3.268 3.268 0 0 0-2.634 3.014l-5.474 92.456a3.268 3.268 0 0 0 3.997 3.378l24.777-5.718c2.318-.535 4.413 1.507 3.936 3.838l-7.361 36.047c-.495 2.426 1.782 4.5 4.151 3.78l15.304-4.649c2.372-.72 4.652 1.36 4.15 3.788l-11.698 56.621c-.732 3.542 3.979 5.473 5.943 2.437l1.313-2.028l72.516-144.72c1.215-2.423-.88-5.186-3.54-4.672l-25.505 4.922c-2.396.462-4.435-1.77-3.759-4.114l16.646-57.705c.677-2.35-1.37-4.583-3.769-4.113Z"></path></svg>
|
|
||||||
|
Before Width: | Height: | Size: 1.5 KiB |
@@ -1,44 +0,0 @@
|
|||||||
#root {
|
|
||||||
max-width: 1280px;
|
|
||||||
margin: 0 auto;
|
|
||||||
padding: 2rem;
|
|
||||||
text-align: center;
|
|
||||||
--cell-background-color: #293136;
|
|
||||||
--cell-color: #cbe3ef;
|
|
||||||
}
|
|
||||||
|
|
||||||
tr:nth-child(even) {
|
|
||||||
background: #F2F2F2;
|
|
||||||
}
|
|
||||||
|
|
||||||
tr:nth-child(odd) {
|
|
||||||
background: #FFF;
|
|
||||||
}
|
|
||||||
|
|
||||||
th, td {
|
|
||||||
padding-left: 1rem;
|
|
||||||
padding-right: 1rem;
|
|
||||||
background-color: var(--cell-background-color);
|
|
||||||
color: var(--cell-color);
|
|
||||||
}
|
|
||||||
|
|
||||||
textarea#query-input {
|
|
||||||
height: 5em;
|
|
||||||
width: 50em;
|
|
||||||
}
|
|
||||||
|
|
||||||
button#query-button {
|
|
||||||
height: 5em;
|
|
||||||
vertical-align: top;
|
|
||||||
}
|
|
||||||
|
|
||||||
table#query-output {
|
|
||||||
float: none;
|
|
||||||
clear: both;
|
|
||||||
}
|
|
||||||
|
|
||||||
p#footer {
|
|
||||||
float: none;
|
|
||||||
clear: both;
|
|
||||||
vertical-align: bottom;
|
|
||||||
}
|
|
||||||
@@ -1,93 +0,0 @@
|
|||||||
import { useEffect, useState, Component } from "react";
|
|
||||||
import { useWebSocket } from "react-use-websocket";
|
|
||||||
import "./App.css";
|
|
||||||
|
|
||||||
export class Home extends Component {
|
|
||||||
static displayName = Home.name;
|
|
||||||
// static sparkdHost = '192.168.50.94';
|
|
||||||
static sparkdHost = "localhost";
|
|
||||||
|
|
||||||
// static websocket_url = 'wss://markets.api.uat.cmegroup.com/marketdatastream/v1';
|
|
||||||
// static access_token = 'eyJhbGciOiJSUzI1NiIsImtpZCI6IjF0al9MTVFrT2pGVzJWQTZvdkZzOEZHUWViMCIsInBpLmF0bSI6IjEifQ.eyJzY29wZSI6W10sImNsaWVudF9pZCI6ImFwaV90aGlua2FscGhhMzIxIiwiYXVkIjoiTnIiLCJqdGkiOiIweERtb3NnU2NHcE5GUEFaIiwic3ViIjoiYXBpX3RoaW5rYWxwaGEzMjEiLCJleHAiOjE2OTgzNTY5OTJ9.uJJEhWpujGiyJ5_lypiIPB8xR0GkuXqydtCugp5xD0hyTuX6VkQdNoY_oaRXXGRCUPCvfsXpU-HfL7pRkcXCjMLTrVIqf3RjD4_gar_ExbIb180nbYMG87E4rOILi5bIin51KOcMNBdHW7pTKwsS221XGyIAKIj65WxD-K8QSVr1HN6s7KDfYdez5dstWWC6xGg_3IZkrJjx8Df1HNT73K9PwUaAl7gcK4_2RSr8L636QbF3_y4Tv26g5IEv90Mc1mnAFPvUGI-nDR4s5Vso0398DlYl_tw-zbQQmn-QuQ6rS_ZIcZKgyoHKwD-3lgV-J1_--ndHdh8qwJC7Hk8ZKg'
|
|
||||||
|
|
||||||
render() {
|
|
||||||
let socket = null;
|
|
||||||
const wsUrl = `ws://localhost:5268/ws`;
|
|
||||||
function connectToSparkd() {
|
|
||||||
// const wsUrl = Home.websocket_url;
|
|
||||||
if (socket == null) {
|
|
||||||
socket = new WebSocket(wsUrl);
|
|
||||||
}
|
|
||||||
if (socket && socket.readyState === WebSocket.OPEN) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const output = document.getElementById("query-output");
|
|
||||||
socket.onopen = (event) => {
|
|
||||||
console.log(`Connected to ${wsUrl}`);
|
|
||||||
};
|
|
||||||
|
|
||||||
socket.onmessage = (event) => {
|
|
||||||
console.log(`Received message: ${event.data}`);
|
|
||||||
output.innerHTML = event.data;
|
|
||||||
};
|
|
||||||
|
|
||||||
socket.onerror = (error) => {
|
|
||||||
console.log(`Error on websocket ${wsUrl}: ${error}`);
|
|
||||||
};
|
|
||||||
|
|
||||||
socket.onclose = (event) => {
|
|
||||||
if (event.wasClean) {
|
|
||||||
const msg = `Connection to ${wsUrl} closed cleanly, code=${event.code}, reason=${event.reason}`;
|
|
||||||
console.log(msg);
|
|
||||||
} else {
|
|
||||||
const msg = `Connection to ${wsUrl} abruptly closed`;
|
|
||||||
console.log(msg);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
function executeQuery() {
|
|
||||||
if (socket == null || !socket) {
|
|
||||||
connectToSparkd();
|
|
||||||
}
|
|
||||||
if (socket == null || !socket) {
|
|
||||||
console.log("Failed to connect to websocket server");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (socket.readyState === WebSocket.OPEN) {
|
|
||||||
const queryInput = document.getElementById("query-input");
|
|
||||||
const rpcReq = {
|
|
||||||
jsonrpc: "2.0",
|
|
||||||
method: "subscribe",
|
|
||||||
params: {
|
|
||||||
query: queryInput.value,
|
|
||||||
},
|
|
||||||
id: 1,
|
|
||||||
};
|
|
||||||
socket.send(JSON.stringify(rpcReq));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div>
|
|
||||||
<h1>Data Stream Query Analyzer</h1>
|
|
||||||
<p>Run queries against the BTS Cloud</p>
|
|
||||||
<div>
|
|
||||||
<textarea id="query-input" placeholder="Enter Query"></textarea>
|
|
||||||
<button id="query-button" onClick={executeQuery}>
|
|
||||||
Execute
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
<table id="query-output"></table>
|
|
||||||
<p id="footer">
|
|
||||||
The <code>ClientApp</code> subdirectory is a standard React
|
|
||||||
application based on the <code>create-react-app</code> template. If
|
|
||||||
you open a command prompt in that directory, you can run{" "}
|
|
||||||
<code>npm</code> commands such as <code>npm test</code> or{" "}
|
|
||||||
<code>npm install</code>.
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--logos" width="35.93" height="32" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 228"><path fill="#00D8FF" d="M210.483 73.824a171.49 171.49 0 0 0-8.24-2.597c.465-1.9.893-3.777 1.273-5.621c6.238-30.281 2.16-54.676-11.769-62.708c-13.355-7.7-35.196.329-57.254 19.526a171.23 171.23 0 0 0-6.375 5.848a155.866 155.866 0 0 0-4.241-3.917C100.759 3.829 77.587-4.822 63.673 3.233C50.33 10.957 46.379 33.89 51.995 62.588a170.974 170.974 0 0 0 1.892 8.48c-3.28.932-6.445 1.924-9.474 2.98C17.309 83.498 0 98.307 0 113.668c0 15.865 18.582 31.778 46.812 41.427a145.52 145.52 0 0 0 6.921 2.165a167.467 167.467 0 0 0-2.01 9.138c-5.354 28.2-1.173 50.591 12.134 58.266c13.744 7.926 36.812-.22 59.273-19.855a145.567 145.567 0 0 0 5.342-4.923a168.064 168.064 0 0 0 6.92 6.314c21.758 18.722 43.246 26.282 56.54 18.586c13.731-7.949 18.194-32.003 12.4-61.268a145.016 145.016 0 0 0-1.535-6.842c1.62-.48 3.21-.974 4.76-1.488c29.348-9.723 48.443-25.443 48.443-41.52c0-15.417-17.868-30.326-45.517-39.844Zm-6.365 70.984c-1.4.463-2.836.91-4.3 1.345c-3.24-10.257-7.612-21.163-12.963-32.432c5.106-11 9.31-21.767 12.459-31.957c2.619.758 5.16 1.557 7.61 2.4c23.69 8.156 38.14 20.213 38.14 29.504c0 9.896-15.606 22.743-40.946 31.14Zm-10.514 20.834c2.562 12.94 2.927 24.64 1.23 33.787c-1.524 8.219-4.59 13.698-8.382 15.893c-8.067 4.67-25.32-1.4-43.927-17.412a156.726 156.726 0 0 1-6.437-5.87c7.214-7.889 14.423-17.06 21.459-27.246c12.376-1.098 24.068-2.894 34.671-5.345a134.17 134.17 0 0 1 1.386 6.193ZM87.276 214.515c-7.882 2.783-14.16 2.863-17.955.675c-8.075-4.657-11.432-22.636-6.853-46.752a156.923 156.923 0 0 1 1.869-8.499c10.486 2.32 22.093 3.988 34.498 4.994c7.084 9.967 14.501 19.128 21.976 27.15a134.668 134.668 0 0 1-4.877 4.492c-9.933 8.682-19.886 14.842-28.658 17.94ZM50.35 144.747c-12.483-4.267-22.792-9.812-29.858-15.863c-6.35-5.437-9.555-10.836-9.555-15.216c0-9.322 13.897-21.212 37.076-29.293c2.813-.98 5.757-1.905 8.812-2.773c3.204 10.42 7.406 21.315 12.477 32.332c-5.137 11.18-9.399 22.249-12.634 32.792a134.718 134.718 0 0 1-6.318-1.979Zm12.378-84.26c-4.811-24.587-1.616-43.134 6.425-47.789c8.564-4.958 27.502 2.111 47.463 19.835a144.318 144.318 0 0 1 3.841 3.545c-7.438 7.987-14.787 17.08-21.808 26.988c-12.04 1.116-23.565 2.908-34.161 5.309a160.342 160.342 0 0 1-1.76-7.887Zm110.427 27.268a347.8 347.8 0 0 0-7.785-12.803c8.168 1.033 15.994 2.404 23.343 4.08c-2.206 7.072-4.956 14.465-8.193 22.045a381.151 381.151 0 0 0-7.365-13.322Zm-45.032-43.861c5.044 5.465 10.096 11.566 15.065 18.186a322.04 322.04 0 0 0-30.257-.006c4.974-6.559 10.069-12.652 15.192-18.18ZM82.802 87.83a323.167 323.167 0 0 0-7.227 13.238c-3.184-7.553-5.909-14.98-8.134-22.152c7.304-1.634 15.093-2.97 23.209-3.984a321.524 321.524 0 0 0-7.848 12.897Zm8.081 65.352c-8.385-.936-16.291-2.203-23.593-3.793c2.26-7.3 5.045-14.885 8.298-22.6a321.187 321.187 0 0 0 7.257 13.246c2.594 4.48 5.28 8.868 8.038 13.147Zm37.542 31.03c-5.184-5.592-10.354-11.779-15.403-18.433c4.902.192 9.899.29 14.978.29c5.218 0 10.376-.117 15.453-.343c-4.985 6.774-10.018 12.97-15.028 18.486Zm52.198-57.817c3.422 7.8 6.306 15.345 8.596 22.52c-7.422 1.694-15.436 3.058-23.88 4.071a382.417 382.417 0 0 0 7.859-13.026a347.403 347.403 0 0 0 7.425-13.565Zm-16.898 8.101a358.557 358.557 0 0 1-12.281 19.815a329.4 329.4 0 0 1-23.444.823c-7.967 0-15.716-.248-23.178-.732a310.202 310.202 0 0 1-12.513-19.846h.001a307.41 307.41 0 0 1-10.923-20.627a310.278 310.278 0 0 1 10.89-20.637l-.001.001a307.318 307.318 0 0 1 12.413-19.761c7.613-.576 15.42-.876 23.31-.876H128c7.926 0 15.743.303 23.354.883a329.357 329.357 0 0 1 12.335 19.695a358.489 358.489 0 0 1 11.036 20.54a329.472 329.472 0 0 1-11 20.722Zm22.56-122.124c8.572 4.944 11.906 24.881 6.52 51.026c-.344 1.668-.73 3.367-1.15 5.09c-10.622-2.452-22.155-4.275-34.23-5.408c-7.034-10.017-14.323-19.124-21.64-27.008a160.789 160.789 0 0 1 5.888-5.4c18.9-16.447 36.564-22.941 44.612-18.3ZM128 90.808c12.625 0 22.86 10.235 22.86 22.86s-10.235 22.86-22.86 22.86s-22.86-10.235-22.86-22.86s10.235-22.86 22.86-22.86Z"></path></svg>
|
|
||||||
|
Before Width: | Height: | Size: 4.0 KiB |
@@ -1,68 +0,0 @@
|
|||||||
:root {
|
|
||||||
font-family: Inter, system-ui, Avenir, Helvetica, Arial, sans-serif;
|
|
||||||
line-height: 1.5;
|
|
||||||
font-weight: 400;
|
|
||||||
|
|
||||||
color-scheme: light dark;
|
|
||||||
color: rgba(255, 255, 255, 0.87);
|
|
||||||
background-color: #242424;
|
|
||||||
|
|
||||||
font-synthesis: none;
|
|
||||||
text-rendering: optimizeLegibility;
|
|
||||||
-webkit-font-smoothing: antialiased;
|
|
||||||
-moz-osx-font-smoothing: grayscale;
|
|
||||||
}
|
|
||||||
|
|
||||||
a {
|
|
||||||
font-weight: 500;
|
|
||||||
color: #646cff;
|
|
||||||
text-decoration: inherit;
|
|
||||||
}
|
|
||||||
a:hover {
|
|
||||||
color: #535bf2;
|
|
||||||
}
|
|
||||||
|
|
||||||
body {
|
|
||||||
margin: 0;
|
|
||||||
display: flex;
|
|
||||||
place-items: center;
|
|
||||||
min-width: 320px;
|
|
||||||
min-height: 100vh;
|
|
||||||
}
|
|
||||||
|
|
||||||
h1 {
|
|
||||||
font-size: 3.2em;
|
|
||||||
line-height: 1.1;
|
|
||||||
}
|
|
||||||
|
|
||||||
button {
|
|
||||||
border-radius: 8px;
|
|
||||||
border: 1px solid transparent;
|
|
||||||
padding: 0.6em 1.2em;
|
|
||||||
font-size: 1em;
|
|
||||||
font-weight: 500;
|
|
||||||
font-family: inherit;
|
|
||||||
background-color: #1a1a1a;
|
|
||||||
cursor: pointer;
|
|
||||||
transition: border-color 0.25s;
|
|
||||||
}
|
|
||||||
button:hover {
|
|
||||||
border-color: #646cff;
|
|
||||||
}
|
|
||||||
button:focus,
|
|
||||||
button:focus-visible {
|
|
||||||
outline: 4px auto -webkit-focus-ring-color;
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (prefers-color-scheme: light) {
|
|
||||||
:root {
|
|
||||||
color: #213547;
|
|
||||||
background-color: #ffffff;
|
|
||||||
}
|
|
||||||
a:hover {
|
|
||||||
color: #747bff;
|
|
||||||
}
|
|
||||||
button {
|
|
||||||
background-color: #f9f9f9;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,10 +0,0 @@
|
|||||||
import React from 'react'
|
|
||||||
import ReactDOM from 'react-dom/client'
|
|
||||||
import {Home} from './App.jsx'
|
|
||||||
import './index.css'
|
|
||||||
|
|
||||||
ReactDOM.createRoot(document.getElementById('root')).render(
|
|
||||||
<React.StrictMode>
|
|
||||||
<Home />
|
|
||||||
</React.StrictMode>,
|
|
||||||
)
|
|
||||||
@@ -1,60 +0,0 @@
|
|||||||
import { fileURLToPath, URL } from 'node:url';
|
|
||||||
|
|
||||||
import { defineConfig } from 'vite';
|
|
||||||
import plugin from '@vitejs/plugin-react';
|
|
||||||
import fs from 'fs';
|
|
||||||
import path from 'path';
|
|
||||||
import child_process from 'child_process';
|
|
||||||
|
|
||||||
const baseFolder =
|
|
||||||
process.env.APPDATA !== undefined && process.env.APPDATA !== ''
|
|
||||||
? `${process.env.APPDATA}/ASP.NET/https`
|
|
||||||
: `${process.env.HOME}/.aspnet/https`;
|
|
||||||
|
|
||||||
const certificateArg = process.argv.map(arg => arg.match(/--name=(?<value>.+)/i)).filter(Boolean)[0];
|
|
||||||
const certificateName = certificateArg ? certificateArg.groups.value : "bts.datastreamwebapp.client";
|
|
||||||
|
|
||||||
if (!certificateName) {
|
|
||||||
console.error('Invalid certificate name. Run this script in the context of an npm/yarn script or pass --name=<<app>> explicitly.')
|
|
||||||
process.exit(-1);
|
|
||||||
}
|
|
||||||
|
|
||||||
const certFilePath = path.join(baseFolder, `${certificateName}.pem`);
|
|
||||||
const keyFilePath = path.join(baseFolder, `${certificateName}.key`);
|
|
||||||
|
|
||||||
if (!fs.existsSync(certFilePath) || !fs.existsSync(keyFilePath)) {
|
|
||||||
if (0 !== child_process.spawnSync('dotnet', [
|
|
||||||
'dev-certs',
|
|
||||||
'https',
|
|
||||||
'--export-path',
|
|
||||||
certFilePath,
|
|
||||||
'--format',
|
|
||||||
'Pem',
|
|
||||||
'--no-password',
|
|
||||||
], { stdio: 'inherit', }).status) {
|
|
||||||
throw new Error("Could not create certificate.");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// https://vitejs.dev/config/
|
|
||||||
export default defineConfig({
|
|
||||||
plugins: [plugin()],
|
|
||||||
resolve: {
|
|
||||||
alias: {
|
|
||||||
'@': fileURLToPath(new URL('./src', import.meta.url))
|
|
||||||
}
|
|
||||||
},
|
|
||||||
server: {
|
|
||||||
proxy: {
|
|
||||||
'^/weatherforecast': {
|
|
||||||
target: 'https://localhost:7128/',
|
|
||||||
secure: false
|
|
||||||
}
|
|
||||||
},
|
|
||||||
port: 5173,
|
|
||||||
https: {
|
|
||||||
key: fs.readFileSync(keyFilePath),
|
|
||||||
cert: fs.readFileSync(certFilePath),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
@@ -1,35 +0,0 @@
|
|||||||
<Project Sdk="Microsoft.NET.Sdk.Web">
|
|
||||||
|
|
||||||
<PropertyGroup>
|
|
||||||
<TargetFramework>net8.0</TargetFramework>
|
|
||||||
<Nullable>enable</Nullable>
|
|
||||||
<ImplicitUsings>enable</ImplicitUsings>
|
|
||||||
<InvariantGlobalization>true</InvariantGlobalization>
|
|
||||||
<SpaRoot>..\Bts.DataStreamWebApp.Client</SpaRoot>
|
|
||||||
<SpaProxyLaunchCommand>npm run dev</SpaProxyLaunchCommand>
|
|
||||||
<SpaProxyServerUrl>https://localhost:5173</SpaProxyServerUrl>
|
|
||||||
</PropertyGroup>
|
|
||||||
|
|
||||||
<ItemGroup>
|
|
||||||
<PackageReference Include="CommandLineParser" Version="2.9.1" />
|
|
||||||
<PackageReference Include="Microsoft.AspNetCore.SpaProxy">
|
|
||||||
<Version>8.*-*</Version>
|
|
||||||
</PackageReference>
|
|
||||||
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
|
|
||||||
<PackageReference Include="Swashbuckle.AspNetCore" Version="6.4.0" />
|
|
||||||
</ItemGroup>
|
|
||||||
|
|
||||||
<ItemGroup>
|
|
||||||
<ProjectReference Include="..\Bts.DataStreamWebApp.Client\Bts.DataStreamWebApp.Client.esproj">
|
|
||||||
<ReferenceOutputAssembly>false</ReferenceOutputAssembly>
|
|
||||||
</ProjectReference>
|
|
||||||
<ProjectReference Include="..\DataStream\DataStream.csproj" />
|
|
||||||
<ProjectReference Include="..\SharedStreams\SharedStreams.csproj" />
|
|
||||||
<ProjectReference Include="..\..\client\spark_api\spark_api.csproj" />
|
|
||||||
<ProjectReference Include="..\..\client\spark_websocket\spark_websocket.csproj" />
|
|
||||||
</ItemGroup>
|
|
||||||
|
|
||||||
<!-- <ItemGroup>
|
|
||||||
<Content Update="wwwroot\alt-index.html" CopyToPublishDirectory="Never" />
|
|
||||||
</ItemGroup> -->
|
|
||||||
</Project>
|
|
||||||
@@ -1,6 +0,0 @@
|
|||||||
@Bts.DataStreamWebApp.Server_HostAddress = http://localhost:5268
|
|
||||||
|
|
||||||
GET {{Bts.DataStreamWebApp.Server_HostAddress}}/weatherforecast/
|
|
||||||
Accept: application/json
|
|
||||||
|
|
||||||
###
|
|
||||||
-117
@@ -1,117 +0,0 @@
|
|||||||
namespace Bts.DataStreamWebApp.Server.Controllers;
|
|
||||||
|
|
||||||
using BCG;
|
|
||||||
|
|
||||||
using bts.api.core;
|
|
||||||
|
|
||||||
using Bts.DataStreamExpressions;
|
|
||||||
|
|
||||||
using Microsoft.AspNetCore.Mvc;
|
|
||||||
|
|
||||||
using Spark.Core.Disposables;
|
|
||||||
|
|
||||||
using System.Net.WebSockets;
|
|
||||||
using System.Text;
|
|
||||||
using System.Text.Json;
|
|
||||||
|
|
||||||
// [Route("[controller]")]
|
|
||||||
[Route("/ws")]
|
|
||||||
public class DataStreamSubscriptionController(ILogger<DataStreamSubscriptionController> logger) : ControllerBase {
|
|
||||||
readonly ILogger<DataStreamSubscriptionController> _logger = logger;
|
|
||||||
IDisposable? streamSubscription;
|
|
||||||
|
|
||||||
[Route("/ws")]
|
|
||||||
public async Task Get() {
|
|
||||||
if (!HttpContext.WebSockets.IsWebSocketRequest) {
|
|
||||||
_logger.LogError("Expected websocket request on path '{}'", HttpContext.Request.Path);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
Console.WriteLine("Establishing websocket connection...");
|
|
||||||
using var webSocket = await HttpContext.WebSockets.AcceptWebSocketAsync();
|
|
||||||
Console.WriteLine($"Websocket connection established.");
|
|
||||||
await runWebSocketServer(null, "bts", string.Empty, webSocket);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static async Task Echo(WebSocket webSocket) {
|
|
||||||
var buffer = new byte[1024 * 4];
|
|
||||||
var receiveResult = await webSocket.ReceiveAsync(
|
|
||||||
new ArraySegment<byte>(buffer), CancellationToken.None);
|
|
||||||
|
|
||||||
while (!receiveResult.CloseStatus.HasValue) {
|
|
||||||
await webSocket.SendAsync(
|
|
||||||
new ArraySegment<byte>(buffer, 0, receiveResult.Count),
|
|
||||||
receiveResult.MessageType,
|
|
||||||
receiveResult.EndOfMessage,
|
|
||||||
CancellationToken.None);
|
|
||||||
|
|
||||||
receiveResult = await webSocket.ReceiveAsync(
|
|
||||||
new ArraySegment<byte>(buffer), CancellationToken.None);
|
|
||||||
}
|
|
||||||
|
|
||||||
await webSocket.CloseAsync(
|
|
||||||
receiveResult.CloseStatus.Value,
|
|
||||||
receiveResult.CloseStatusDescription,
|
|
||||||
CancellationToken.None);
|
|
||||||
}
|
|
||||||
|
|
||||||
async Task<int> runWebSocketServer(string? sparkd, string user, string password, WebSocket webSocket) {
|
|
||||||
var context = ServiceContext.create(sparkd);
|
|
||||||
await context.login(user, password);
|
|
||||||
var buffer = new byte[1024 * 4];
|
|
||||||
var receiveResult = await webSocket.ReceiveAsync(
|
|
||||||
new ArraySegment<byte>(buffer), CancellationToken.None);
|
|
||||||
|
|
||||||
try {
|
|
||||||
while (!receiveResult.CloseStatus.HasValue) {
|
|
||||||
receiveResult = await webSocket.ReceiveAsync(
|
|
||||||
new ArraySegment<byte>(buffer), CancellationToken.None);
|
|
||||||
var msg = Encoding.UTF8.GetString(buffer, 0, receiveResult.Count);
|
|
||||||
// var queryArgs = new QueryArguments { Query = msg };
|
|
||||||
var rpcReq = JsonSerializer.Deserialize<Bts.Websocket.JsonRpcRequest<DataStreamSubscription>>(msg);
|
|
||||||
var queryArgs = rpcReq!.Params;
|
|
||||||
// if (queryArgs is null) {
|
|
||||||
// Console.WriteLine("Invalid queryArgs.");
|
|
||||||
// continue;
|
|
||||||
// }
|
|
||||||
var exp = DataStreamExpressionsInterpreter.Evaluate(queryArgs.Query!);
|
|
||||||
if (exp is null) {
|
|
||||||
Console.WriteLine($"Invalid query expression: {queryArgs.Query}.");
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
var vs = exp.getView(new Dictionary<string, StatementExpression>(0));
|
|
||||||
Disposable.dispose(ref streamSubscription);
|
|
||||||
vs.onChange += onStreamChanged;
|
|
||||||
streamSubscription = Disposable.Create(() => vs.onChange -= onStreamChanged);
|
|
||||||
// await webSocket.SendAsync(
|
|
||||||
// new ArraySegment<byte>(buffer, 0, receiveResult.Count),
|
|
||||||
// receiveResult.MessageType,
|
|
||||||
// receiveResult.EndOfMessage,
|
|
||||||
// CancellationToken.None);
|
|
||||||
}
|
|
||||||
} catch (Exception ex) {
|
|
||||||
Console.WriteLine($"Exception on WebsocketServer thread: {ex}");
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
return 0;
|
|
||||||
|
|
||||||
async void onStreamChanged(object newValue) {
|
|
||||||
if (newValue is not DataPacket dp) {
|
|
||||||
Console.WriteLine(
|
|
||||||
$"OnStreamChanged delegate expected DataPacket (actually received {newValue?.GetType()}.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
Console.WriteLine("Received stream update.");
|
|
||||||
var added_rows_str = dp.addedRows.get_printed_rows(val => val.Length);
|
|
||||||
var msg = $"Received new rows:\n{added_rows_str}";
|
|
||||||
// var payload = new ReadOnlyMemory<byte>([.. msg]);
|
|
||||||
var encoded = Encoding.UTF8.GetBytes(msg);
|
|
||||||
var payload = new ArraySegment<byte>(encoded, 0, encoded.Length);
|
|
||||||
await webSocket.SendAsync(
|
|
||||||
payload,
|
|
||||||
receiveResult.MessageType,
|
|
||||||
receiveResult.EndOfMessage,
|
|
||||||
CancellationToken.None);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
-22
@@ -1,22 +0,0 @@
|
|||||||
namespace Bts.DataStreamWebApp.Server.Controllers;
|
|
||||||
using Microsoft.AspNetCore.Mvc;
|
|
||||||
|
|
||||||
[ApiController]
|
|
||||||
[Route("[controller]")]
|
|
||||||
public class WeatherForecastController(ILogger<WeatherForecastController> logger) : ControllerBase {
|
|
||||||
static readonly string[] Summaries = [
|
|
||||||
"Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", "Balmy", "Hot", "Sweltering", "Scorching"
|
|
||||||
];
|
|
||||||
|
|
||||||
readonly ILogger<WeatherForecastController> _logger = logger;
|
|
||||||
|
|
||||||
[HttpGet(Name = "GetWeatherForecast")]
|
|
||||||
public IEnumerable<WeatherForecast> Get() {
|
|
||||||
return Enumerable.Range(1, 5).Select(index => new WeatherForecast {
|
|
||||||
Date = DateOnly.FromDateTime(DateTime.Now.AddDays(index)),
|
|
||||||
TemperatureC = Random.Shared.Next(-20, 55),
|
|
||||||
Summary = Summaries[Random.Shared.Next(Summaries.Length)]
|
|
||||||
})
|
|
||||||
.ToArray();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,8 +0,0 @@
|
|||||||
namespace Bts.DataStreamWebApp.Server;
|
|
||||||
|
|
||||||
using System.Text.Json.Serialization;
|
|
||||||
|
|
||||||
public class DataStreamSubscription {
|
|
||||||
[JsonPropertyName("query")]
|
|
||||||
public string? Query { get; set; }
|
|
||||||
}
|
|
||||||
@@ -1,8 +0,0 @@
|
|||||||
namespace Bts.DataStreamWebApp.Server;
|
|
||||||
|
|
||||||
using BCG;
|
|
||||||
|
|
||||||
class DataStreamUpdate {
|
|
||||||
public List<IBlueRow> addedRows { get; set; } = new(0);
|
|
||||||
public List<IBlueRow> deletedRows { get; set; } = new(0);
|
|
||||||
}
|
|
||||||
@@ -1,14 +0,0 @@
|
|||||||
namespace Bts.DataStreamWebApp.Server;
|
|
||||||
|
|
||||||
using CommandLine;
|
|
||||||
|
|
||||||
class DataStreamWebAppServerOptions {
|
|
||||||
[Option("sparkd")]
|
|
||||||
public string? sparkd { get; set; }
|
|
||||||
|
|
||||||
[Option("user", Default = "bts")]
|
|
||||||
public string? user { get; set; }
|
|
||||||
|
|
||||||
[Option("password", Default = "")]
|
|
||||||
public string? password { get; set; }
|
|
||||||
}
|
|
||||||
@@ -1,58 +0,0 @@
|
|||||||
using Microsoft.AspNetCore.Http;
|
|
||||||
using Microsoft.Extensions.Logging;
|
|
||||||
|
|
||||||
using System;
|
|
||||||
using System.Net.WebSockets;
|
|
||||||
using System.Threading;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
|
|
||||||
namespace Bts.DataStreamWebApp.Server {
|
|
||||||
// public class DataStreamWebSocketHandler {
|
|
||||||
// readonly WebSocketConnectionManager _connectionManager = new();
|
|
||||||
// readonly ILogger<DataStreamWebSocketHandler> _logger;
|
|
||||||
|
|
||||||
// public DataStreamWebSocketHandler(
|
|
||||||
// WebSocketConnectionManager connectionManager,
|
|
||||||
// ILogger<DataStreamWebSocketHandler> logger
|
|
||||||
// ) {
|
|
||||||
// _connectionManager = connectionManager;
|
|
||||||
// _logger = logger;
|
|
||||||
// }
|
|
||||||
|
|
||||||
// public async Task HandleWebSocket(HttpContext context, WebSocket webSocket) {
|
|
||||||
// var socketId = _connectionManager.AddSocket(webSocket);
|
|
||||||
|
|
||||||
// _logger.LogInformation($"WebSocket connection established with ID {socketId}");
|
|
||||||
|
|
||||||
// while (webSocket.State == WebSocketState.Open) {
|
|
||||||
// var message = await ReceiveMessageAsync(webSocket);
|
|
||||||
// if (message != null) {
|
|
||||||
// _logger.LogInformation($"Received message from ID {socketId}: {message}");
|
|
||||||
// await BroadcastMessageAsync(message);
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
// _connectionManager.RemoveSocket(socketId);
|
|
||||||
// _logger.LogInformation($"WebSocket connection closed with ID {socketId}");
|
|
||||||
// }
|
|
||||||
|
|
||||||
// async Task<string?> ReceiveMessageAsync(WebSocket webSocket) {
|
|
||||||
// var buffer = new byte[1024];
|
|
||||||
// var result = await webSocket.ReceiveAsync(new ArraySegment<byte>(buffer), CancellationToken.None);
|
|
||||||
|
|
||||||
// if (result.CloseStatus.HasValue) {
|
|
||||||
// return null;
|
|
||||||
// }
|
|
||||||
|
|
||||||
// return System.Text.Encoding.UTF8.GetString(buffer, 0, result.Count);
|
|
||||||
// }
|
|
||||||
|
|
||||||
// async Task BroadcastMessageAsync(string message) {
|
|
||||||
// foreach (var socket in _connectionManager.GetAllSockets()) {
|
|
||||||
// if (socket.Value.State == WebSocketState.Open) {
|
|
||||||
// await socket.Value.SendAsync(System.Text.Encoding.UTF8.GetBytes(message), WebSocketMessageType.Text, true, CancellationToken.None);
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
}
|
|
||||||
@@ -1,166 +0,0 @@
|
|||||||
using BCG;
|
|
||||||
|
|
||||||
using bts;
|
|
||||||
using bts.api.core;
|
|
||||||
using bts.utils;
|
|
||||||
|
|
||||||
using Bts.DataStreamExpressions;
|
|
||||||
using Bts.DataStreamWebApp.Server;
|
|
||||||
using Bts.DataStreamWebApp.Server.Controllers;
|
|
||||||
|
|
||||||
using CommandLine;
|
|
||||||
|
|
||||||
using mbts.config;
|
|
||||||
|
|
||||||
using System.Net.WebSockets;
|
|
||||||
using System.Reactive.Disposables;
|
|
||||||
using System.Reactive.Linq;
|
|
||||||
using System.Text;
|
|
||||||
using System.Text.Json;
|
|
||||||
using System.Text.Json.Serialization;
|
|
||||||
|
|
||||||
static string fmtReq(HttpRequest req) =>
|
|
||||||
$"{req.Protocol}://{req.Host}/{req.Path}?{req.QueryString}";
|
|
||||||
|
|
||||||
static string fmtDataPacket(DataPacket dp) {
|
|
||||||
var sb = new StringBuilder();
|
|
||||||
var rows = dp.addedRows.Cast<IBlueRow>().ToList();
|
|
||||||
var colWidths = dp.schema.columnInfo.Select(c => rows.Max(r => r[c.colNo]?.ToString()?.Length ?? 0)).ToArray();
|
|
||||||
foreach (IBlueRow r in dp.addedRows) {
|
|
||||||
for (int i = 0; i < colWidths.Length; i++) {
|
|
||||||
var str = r[i]?.ToString() ?? string.Empty;
|
|
||||||
if (i > 0) {
|
|
||||||
sb.Append(" | ");
|
|
||||||
}
|
|
||||||
sb.Append(str);
|
|
||||||
var width = colWidths[i];
|
|
||||||
if (str.Length < width) {
|
|
||||||
sb.Append(new string(' ', width - str.Length));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
sb.AppendLine();
|
|
||||||
}
|
|
||||||
return sb.ToString();
|
|
||||||
// var upd = new DataStreamUpdate();
|
|
||||||
// upd.addedRows.AddRange(dp.addedRows.Cast<IBlueRow>());
|
|
||||||
// upd.deletedRows.AddRange(dp.deletedRows.Cast<IBlueRow>());
|
|
||||||
// return fmtDataStreamUpdate(upd);
|
|
||||||
}
|
|
||||||
|
|
||||||
static string fmtDataStreamUpdate(DataStreamUpdate rows) {
|
|
||||||
return JsonSerializer.Serialize(rows);
|
|
||||||
}
|
|
||||||
|
|
||||||
var parser = new Parser(ps => {
|
|
||||||
ps.CaseInsensitiveEnumValues = true;
|
|
||||||
ps.HelpWriter = Console.Out;
|
|
||||||
});
|
|
||||||
|
|
||||||
await parser.ParseArguments<DataStreamWebAppServerOptions>(args).
|
|
||||||
MapResult(run, errors => Task.FromResult(1));
|
|
||||||
|
|
||||||
async Task<int> run(DataStreamWebAppServerOptions opts) {
|
|
||||||
BTSNativeMethods.provide_command_line(args, args.Length);
|
|
||||||
Configuration.initialize();
|
|
||||||
var context = ServiceContext.create(opts.sparkd);
|
|
||||||
await context.login(opts.user!, opts.password!);
|
|
||||||
var vsWorkLoop = Observable.Interval(TimeSpan.FromMilliseconds(200)).Subscribe(_ => {
|
|
||||||
ViewStreamDatabase.instance.doWork();
|
|
||||||
MasterWorker.getInstance(false).doWork();
|
|
||||||
});
|
|
||||||
var builder = WebApplication.CreateBuilder(args);
|
|
||||||
builder.Services.AddDirectoryBrowser();
|
|
||||||
var app = builder.Build();
|
|
||||||
var wsOpts = new WebSocketOptions {
|
|
||||||
KeepAliveInterval = TimeSpan.FromMinutes(2)
|
|
||||||
};
|
|
||||||
app.UseWebSockets(wsOpts);
|
|
||||||
app.UseDefaultFiles();
|
|
||||||
app.UseStaticFiles();
|
|
||||||
app.MapFallbackToFile("/index.html");
|
|
||||||
|
|
||||||
app.Use(async (context, next) => {
|
|
||||||
var reqDesc = fmtReq(context.Request);
|
|
||||||
Console.WriteLine($"Webserver received: {reqDesc}");
|
|
||||||
if (context.Request.Path == "/ws") {
|
|
||||||
if (context.WebSockets.IsWebSocketRequest) {
|
|
||||||
Console.WriteLine("Accepting websocket connection...");
|
|
||||||
using var webSocket = await context.WebSockets.AcceptWebSocketAsync();
|
|
||||||
Console.WriteLine("Websocket connection accepted.");
|
|
||||||
await runWebSocketServer(
|
|
||||||
opts.sparkd, opts.user!, opts.password!, webSocket);
|
|
||||||
} else {
|
|
||||||
context.Response.StatusCode = StatusCodes.Status400BadRequest;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
Console.WriteLine($"Webserver ignoring req: {reqDesc}");
|
|
||||||
await next(context);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
try {
|
|
||||||
await app.RunAsync();
|
|
||||||
} catch (Exception ex) {
|
|
||||||
Console.WriteLine($"Exception on DataStreamWebsocketServer thread: {ex}");
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
Spark.Core.Disposables.Disposable.dispose(ref vsWorkLoop);
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
async Task<int> runWebSocketServer(string? sparkd, string user, string password, WebSocket webSocket) {
|
|
||||||
var buffer = new byte[1024 * 4];
|
|
||||||
var receiveResult = await webSocket.ReceiveAsync(
|
|
||||||
new ArraySegment<byte>(buffer), CancellationToken.None);
|
|
||||||
|
|
||||||
try {
|
|
||||||
while (!receiveResult.CloseStatus.HasValue) {
|
|
||||||
Console.WriteLine("Awaiting websocket message from client");
|
|
||||||
receiveResult = await webSocket.ReceiveAsync(
|
|
||||||
new ArraySegment<byte>(buffer), CancellationToken.None);
|
|
||||||
var msg = Encoding.UTF8.GetString(buffer, 0, receiveResult.Count);
|
|
||||||
var rpcReq = JsonSerializer.Deserialize<Bts.Websocket.JsonRpcRequest<DataStreamSubscription>>(msg);
|
|
||||||
var queryArgs = rpcReq!.Params!;
|
|
||||||
// var queryArgs = new DataStreamSubscription { Query = msg };
|
|
||||||
var exp = DataStreamExpressionsInterpreter.Evaluate(queryArgs.Query!);
|
|
||||||
if (exp is null) {
|
|
||||||
Console.WriteLine($"Invalid query expression: {queryArgs.Query}.");
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
var vs = exp.getView(new Dictionary<string, StatementExpression>(0));
|
|
||||||
Console.WriteLine("Subscribing to stream...");
|
|
||||||
vs.onChange += onStreamChanged;
|
|
||||||
|
|
||||||
await webSocket.SendAsync(
|
|
||||||
new ArraySegment<byte>(buffer, 0, receiveResult.Count),
|
|
||||||
receiveResult.MessageType,
|
|
||||||
receiveResult.EndOfMessage,
|
|
||||||
CancellationToken.None);
|
|
||||||
}
|
|
||||||
} catch (Exception ex) {
|
|
||||||
Console.WriteLine($"Exception on WebsocketServer thread: {ex}");
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
return 0;
|
|
||||||
|
|
||||||
async void onStreamChanged(object newValue) {
|
|
||||||
if (newValue is not DataPacket dp) {
|
|
||||||
Console.WriteLine(
|
|
||||||
$"OnStreamChanged delegate expected DataPacket (actually received {newValue?.GetType()}.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
Console.WriteLine("Received stream update.");
|
|
||||||
var sb = new StringBuilder();
|
|
||||||
var updateDesc = fmtDataPacket(dp);
|
|
||||||
var msg = $"Received new rows:\n{updateDesc}";
|
|
||||||
// var payload = new ReadOnlyMemory<byte>([.. msg]);
|
|
||||||
var encoded = Encoding.UTF8.GetBytes(msg);
|
|
||||||
var payload = new ArraySegment<byte>(encoded, 0, encoded.Length);
|
|
||||||
await webSocket.SendAsync(
|
|
||||||
payload,
|
|
||||||
receiveResult.MessageType,
|
|
||||||
receiveResult.EndOfMessage,
|
|
||||||
CancellationToken.None);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,48 +0,0 @@
|
|||||||
{
|
|
||||||
"profiles": {
|
|
||||||
"default": {
|
|
||||||
"commandName": "Project",
|
|
||||||
"launchBrowser": true,
|
|
||||||
"applicationUrl": "http://localhost:5400"
|
|
||||||
},
|
|
||||||
"http": {
|
|
||||||
"commandName": "Project",
|
|
||||||
"launchBrowser": true,
|
|
||||||
"launchUrl": "swagger",
|
|
||||||
"environmentVariables": {
|
|
||||||
"ASPNETCORE_ENVIRONMENT": "Development",
|
|
||||||
"ASPNETCORE_HOSTINGSTARTUPASSEMBLIES": "Microsoft.AspNetCore.SpaProxy"
|
|
||||||
},
|
|
||||||
"dotnetRunMessages": true,
|
|
||||||
"applicationUrl": "http://localhost:5268"
|
|
||||||
},
|
|
||||||
"https": {
|
|
||||||
"commandName": "Project",
|
|
||||||
"launchUrl": "swagger",
|
|
||||||
"environmentVariables": {
|
|
||||||
"ASPNETCORE_ENVIRONMENT": "Development",
|
|
||||||
"ASPNETCORE_HOSTINGSTARTUPASSEMBLIES": "Microsoft.AspNetCore.SpaProxy"
|
|
||||||
},
|
|
||||||
"dotnetRunMessages": true,
|
|
||||||
"applicationUrl": "https://localhost:7128;http://localhost:5268"
|
|
||||||
},
|
|
||||||
"IIS Express": {
|
|
||||||
"commandName": "IISExpress",
|
|
||||||
"launchBrowser": true,
|
|
||||||
"launchUrl": "swagger",
|
|
||||||
"environmentVariables": {
|
|
||||||
"ASPNETCORE_ENVIRONMENT": "Development",
|
|
||||||
"ASPNETCORE_HOSTINGSTARTUPASSEMBLIES": "Microsoft.AspNetCore.SpaProxy"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"$schema": "http://json.schemastore.org/launchsettings.json",
|
|
||||||
"iisSettings": {
|
|
||||||
"windowsAuthentication": false,
|
|
||||||
"anonymousAuthentication": true,
|
|
||||||
"iisExpress": {
|
|
||||||
"applicationUrl": "http://localhost:2083",
|
|
||||||
"sslPort": 44383
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,12 +0,0 @@
|
|||||||
namespace Bts.DataStreamWebApp.Server;
|
|
||||||
|
|
||||||
public class WeatherForecast
|
|
||||||
{
|
|
||||||
public DateOnly Date { get; set; }
|
|
||||||
|
|
||||||
public int TemperatureC { get; set; }
|
|
||||||
|
|
||||||
public int TemperatureF => 32 + (int)(TemperatureC / 0.5556);
|
|
||||||
|
|
||||||
public string? Summary { get; set; }
|
|
||||||
}
|
|
||||||
@@ -1,37 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.Collections.Concurrent;
|
|
||||||
using System.Net.WebSockets;
|
|
||||||
|
|
||||||
namespace Bts.DataStreamWebApp.Server {
|
|
||||||
// public class WebSocketConnectionManager {
|
|
||||||
// private readonly ConcurrentDictionary<Guid, WebSocket> _sockets = new ConcurrentDictionary<Guid, WebSocket>();
|
|
||||||
|
|
||||||
// public Guid AddSocket(WebSocket socket) {
|
|
||||||
// var socketId = Guid.NewGuid();
|
|
||||||
// _sockets.TryAdd(socketId, socket);
|
|
||||||
// return socketId;
|
|
||||||
// }
|
|
||||||
|
|
||||||
// public WebSocket? GetSocket(Guid socketId) {
|
|
||||||
// _sockets.TryGetValue(socketId, out var socket);
|
|
||||||
// return socket;
|
|
||||||
// }
|
|
||||||
|
|
||||||
// public ConcurrentDictionary<Guid, WebSocket>.ValueCollection GetAllSockets() {
|
|
||||||
// return _sockets.Values;
|
|
||||||
// }
|
|
||||||
|
|
||||||
// public Guid? GetSocketId(WebSocket socket) {
|
|
||||||
// foreach (var (key, value) in _sockets) {
|
|
||||||
// if (value == socket) {
|
|
||||||
// return key;
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// return null;
|
|
||||||
// }
|
|
||||||
|
|
||||||
// public void RemoveSocket(Guid socketId) {
|
|
||||||
// _sockets.TryRemove(socketId, out _);
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
}
|
|
||||||
@@ -1,8 +0,0 @@
|
|||||||
{
|
|
||||||
"Logging": {
|
|
||||||
"LogLevel": {
|
|
||||||
"Default": "Information",
|
|
||||||
"Microsoft.AspNetCore": "Warning"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,9 +0,0 @@
|
|||||||
{
|
|
||||||
"Logging": {
|
|
||||||
"LogLevel": {
|
|
||||||
"Default": "Information",
|
|
||||||
"Microsoft.AspNetCore": "Warning"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"AllowedHosts": "*"
|
|
||||||
}
|
|
||||||
@@ -1,168 +0,0 @@
|
|||||||
<!DOCTYPE html>
|
|
||||||
<html>
|
|
||||||
<head>
|
|
||||||
<meta charset="utf-8" />
|
|
||||||
<title></title>
|
|
||||||
<style>
|
|
||||||
table {
|
|
||||||
border: 0
|
|
||||||
}
|
|
||||||
|
|
||||||
.commslog-data {
|
|
||||||
font-family: Consolas, Courier New, Courier, monospace;
|
|
||||||
}
|
|
||||||
|
|
||||||
.commslog-server {
|
|
||||||
background-color: red;
|
|
||||||
color: white
|
|
||||||
}
|
|
||||||
|
|
||||||
.commslog-client {
|
|
||||||
background-color: green;
|
|
||||||
color: white
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<h1>ALT WebSocket Sample Application</h1>
|
|
||||||
<p id="stateLabel">Ready to connect...</p>
|
|
||||||
<div>
|
|
||||||
<label for="connectionUrl">WebSocket Server URL:</label>
|
|
||||||
<input id="connectionUrl" />
|
|
||||||
<button id="connectButton" type="submit">Connect</button>
|
|
||||||
</div>
|
|
||||||
<p></p>
|
|
||||||
<div>
|
|
||||||
<label for="sendMessage">Message to send:</label>
|
|
||||||
<input id="sendMessage" disabled />
|
|
||||||
<button id="sendButton" type="submit" disabled>Send</button>
|
|
||||||
<button id="closeButton" disabled>Close Socket</button>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<h2>Communication Log</h2>
|
|
||||||
<table style="width: 800px">
|
|
||||||
<thead>
|
|
||||||
<tr>
|
|
||||||
<td style="width: 100px">From</td>
|
|
||||||
<td style="width: 100px">To</td>
|
|
||||||
<td>Data</td>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody id="commsLog">
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
var connectionUrl = document.getElementById("connectionUrl");
|
|
||||||
var connectButton = document.getElementById("connectButton");
|
|
||||||
var stateLabel = document.getElementById("stateLabel");
|
|
||||||
var sendMessage = document.getElementById("sendMessage");
|
|
||||||
var sendButton = document.getElementById("sendButton");
|
|
||||||
var commsLog = document.getElementById("commsLog");
|
|
||||||
var closeButton = document.getElementById("closeButton");
|
|
||||||
var socket;
|
|
||||||
|
|
||||||
var scheme = document.location.protocol === "https:" ? "wss" : "ws";
|
|
||||||
var port = document.location.port ? (":" + document.location.port) : "";
|
|
||||||
|
|
||||||
connectionUrl.value = scheme + "://" + document.location.hostname + port + "/ws" ;
|
|
||||||
|
|
||||||
function updateState() {
|
|
||||||
function disable() {
|
|
||||||
sendMessage.disabled = true;
|
|
||||||
sendButton.disabled = true;
|
|
||||||
closeButton.disabled = true;
|
|
||||||
}
|
|
||||||
function enable() {
|
|
||||||
sendMessage.disabled = false;
|
|
||||||
sendButton.disabled = false;
|
|
||||||
closeButton.disabled = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
connectionUrl.disabled = true;
|
|
||||||
connectButton.disabled = true;
|
|
||||||
|
|
||||||
if (!socket) {
|
|
||||||
disable();
|
|
||||||
} else {
|
|
||||||
switch (socket.readyState) {
|
|
||||||
case WebSocket.CLOSED:
|
|
||||||
stateLabel.innerHTML = "Closed";
|
|
||||||
disable();
|
|
||||||
connectionUrl.disabled = false;
|
|
||||||
connectButton.disabled = false;
|
|
||||||
break;
|
|
||||||
case WebSocket.CLOSING:
|
|
||||||
stateLabel.innerHTML = "Closing...";
|
|
||||||
disable();
|
|
||||||
break;
|
|
||||||
case WebSocket.CONNECTING:
|
|
||||||
stateLabel.innerHTML = "Connecting...";
|
|
||||||
disable();
|
|
||||||
break;
|
|
||||||
case WebSocket.OPEN:
|
|
||||||
stateLabel.innerHTML = "Open";
|
|
||||||
enable();
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
stateLabel.innerHTML = "Unknown WebSocket State: " + htmlEscape(socket.readyState);
|
|
||||||
disable();
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
closeButton.onclick = function () {
|
|
||||||
if (!socket || socket.readyState !== WebSocket.OPEN) {
|
|
||||||
alert("socket not connected");
|
|
||||||
}
|
|
||||||
socket.close(1000, "Closing from client");
|
|
||||||
};
|
|
||||||
|
|
||||||
sendButton.onclick = function () {
|
|
||||||
if (!socket || socket.readyState !== WebSocket.OPEN) {
|
|
||||||
alert("socket not connected");
|
|
||||||
}
|
|
||||||
var data = sendMessage.value;
|
|
||||||
socket.send(data);
|
|
||||||
commsLog.innerHTML += '<tr>' +
|
|
||||||
'<td class="commslog-client">Client</td>' +
|
|
||||||
'<td class="commslog-server">Server</td>' +
|
|
||||||
'<td class="commslog-data">' + htmlEscape(data) + '</td></tr>';
|
|
||||||
};
|
|
||||||
|
|
||||||
connectButton.onclick = function() {
|
|
||||||
stateLabel.innerHTML = "Connecting";
|
|
||||||
socket = new WebSocket(connectionUrl.value);
|
|
||||||
socket.onopen = function (event) {
|
|
||||||
updateState();
|
|
||||||
commsLog.innerHTML += '<tr>' +
|
|
||||||
'<td colspan="3" class="commslog-data">Connection opened</td>' +
|
|
||||||
'</tr>';
|
|
||||||
};
|
|
||||||
socket.onclose = function (event) {
|
|
||||||
updateState();
|
|
||||||
commsLog.innerHTML += '<tr>' +
|
|
||||||
'<td colspan="3" class="commslog-data">Connection closed. Code: ' + htmlEscape(event.code) + '. Reason: ' + htmlEscape(event.reason) + '</td>' +
|
|
||||||
'</tr>';
|
|
||||||
};
|
|
||||||
socket.onerror = updateState;
|
|
||||||
socket.onmessage = function (event) {
|
|
||||||
commsLog.innerHTML += '<tr>' +
|
|
||||||
'<td class="commslog-server">Server</td>' +
|
|
||||||
'<td class="commslog-client">Client</td>' +
|
|
||||||
'<td class="commslog-data">' + htmlEscape(event.data) + '</td></tr>';
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
function htmlEscape(str) {
|
|
||||||
return str.toString()
|
|
||||||
.replace(/&/g, '&')
|
|
||||||
.replace(/"/g, '"')
|
|
||||||
.replace(/'/g, ''')
|
|
||||||
.replace(/</g, '<')
|
|
||||||
.replace(/>/g, '>');
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
Binary file not shown.
|
Before Width: | Height: | Size: 8.2 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 1.1 KiB |
@@ -1,176 +0,0 @@
|
|||||||
<!DOCTYPE html>
|
|
||||||
<html>
|
|
||||||
<head>
|
|
||||||
<meta charset="utf-8" />
|
|
||||||
<title></title>
|
|
||||||
<style>
|
|
||||||
table {
|
|
||||||
border: 0
|
|
||||||
}
|
|
||||||
|
|
||||||
.commslog-data {
|
|
||||||
font-family: Consolas, Courier New, Courier, monospace;
|
|
||||||
}
|
|
||||||
|
|
||||||
.commslog-server {
|
|
||||||
background-color: red;
|
|
||||||
color: white
|
|
||||||
}
|
|
||||||
|
|
||||||
.commslog-client {
|
|
||||||
background-color: green;
|
|
||||||
color: white
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<h1>DataStreams via WebSocket</h1>
|
|
||||||
<p id="stateLabel">Ready to connect...</p>
|
|
||||||
<div>
|
|
||||||
<label for="connectionUrl">WebSocket Server URL:</label>
|
|
||||||
<input id="connectionUrl" />
|
|
||||||
<button id="connectButton" type="submit">Connect</button>
|
|
||||||
</div>
|
|
||||||
<p></p>
|
|
||||||
<div>
|
|
||||||
<label for="sendMessage">Query:</label>
|
|
||||||
<input id="sendMessage" disabled />
|
|
||||||
<button id="sendButton" type="submit" disabled>Send</button>
|
|
||||||
<button id="closeButton" disabled>Close Socket</button>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<h2>Communication Log</h2>
|
|
||||||
<table style="width: 800px">
|
|
||||||
<thead>
|
|
||||||
<tr>
|
|
||||||
<td style="width: 100px">From</td>
|
|
||||||
<td style="width: 100px">To</td>
|
|
||||||
<td>Data</td>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody id="commsLog">
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
var connectionUrl = document.getElementById("connectionUrl");
|
|
||||||
var connectButton = document.getElementById("connectButton");
|
|
||||||
var stateLabel = document.getElementById("stateLabel");
|
|
||||||
var sendMessage = document.getElementById("sendMessage");
|
|
||||||
var sendButton = document.getElementById("sendButton");
|
|
||||||
var commsLog = document.getElementById("commsLog");
|
|
||||||
var closeButton = document.getElementById("closeButton");
|
|
||||||
var socket;
|
|
||||||
|
|
||||||
var scheme = document.location.protocol === "https:" ? "wss" : "ws";
|
|
||||||
var port = document.location.port ? (":" + document.location.port) : "";
|
|
||||||
|
|
||||||
connectionUrl.value = scheme + "://" + document.location.hostname + port + "/ws" ;
|
|
||||||
|
|
||||||
function updateState() {
|
|
||||||
function disable() {
|
|
||||||
sendMessage.disabled = true;
|
|
||||||
sendButton.disabled = true;
|
|
||||||
closeButton.disabled = true;
|
|
||||||
}
|
|
||||||
function enable() {
|
|
||||||
sendMessage.disabled = false;
|
|
||||||
sendButton.disabled = false;
|
|
||||||
closeButton.disabled = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
connectionUrl.disabled = true;
|
|
||||||
connectButton.disabled = true;
|
|
||||||
|
|
||||||
if (!socket) {
|
|
||||||
disable();
|
|
||||||
} else {
|
|
||||||
switch (socket.readyState) {
|
|
||||||
case WebSocket.CLOSED:
|
|
||||||
stateLabel.innerHTML = "Closed";
|
|
||||||
disable();
|
|
||||||
connectionUrl.disabled = false;
|
|
||||||
connectButton.disabled = false;
|
|
||||||
break;
|
|
||||||
case WebSocket.CLOSING:
|
|
||||||
stateLabel.innerHTML = "Closing...";
|
|
||||||
disable();
|
|
||||||
break;
|
|
||||||
case WebSocket.CONNECTING:
|
|
||||||
stateLabel.innerHTML = "Connecting...";
|
|
||||||
disable();
|
|
||||||
break;
|
|
||||||
case WebSocket.OPEN:
|
|
||||||
stateLabel.innerHTML = "Open";
|
|
||||||
enable();
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
stateLabel.innerHTML = "Unknown WebSocket State: " + htmlEscape(socket.readyState);
|
|
||||||
disable();
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
closeButton.onclick = function () {
|
|
||||||
if (!socket || socket.readyState !== WebSocket.OPEN) {
|
|
||||||
alert("socket not connected");
|
|
||||||
}
|
|
||||||
socket.close(1000, "Closing from client");
|
|
||||||
};
|
|
||||||
|
|
||||||
sendButton.onclick = function () {
|
|
||||||
if (!socket || socket.readyState !== WebSocket.OPEN) {
|
|
||||||
alert("socket not connected");
|
|
||||||
}
|
|
||||||
const rpcReq = {
|
|
||||||
jsonrpc: "2.0",
|
|
||||||
method: "subscribe",
|
|
||||||
params: {
|
|
||||||
query: sendMessage.value,
|
|
||||||
},
|
|
||||||
id: 1,
|
|
||||||
};
|
|
||||||
const rpcReqStr = JSON.stringify(rpcReq);
|
|
||||||
socket.send(rpcReqStr);
|
|
||||||
commsLog.innerHTML += '<tr>' +
|
|
||||||
'<td class="commslog-client">Client</td>' +
|
|
||||||
'<td class="commslog-server">Server</td>' +
|
|
||||||
'<td class="commslog-data">' + htmlEscape(rpcReqStr) + '</td></tr>';
|
|
||||||
};
|
|
||||||
|
|
||||||
connectButton.onclick = function() {
|
|
||||||
stateLabel.innerHTML = "Connecting";
|
|
||||||
socket = new WebSocket(connectionUrl.value);
|
|
||||||
socket.onopen = function (event) {
|
|
||||||
updateState();
|
|
||||||
commsLog.innerHTML += '<tr>' +
|
|
||||||
'<td colspan="3" class="commslog-data">Connection opened</td>' +
|
|
||||||
'</tr>';
|
|
||||||
};
|
|
||||||
socket.onclose = function (event) {
|
|
||||||
updateState();
|
|
||||||
commsLog.innerHTML += '<tr>' +
|
|
||||||
'<td colspan="3" class="commslog-data">Connection closed. Code: ' + htmlEscape(event.code) + '. Reason: ' + htmlEscape(event.reason) + '</td>' +
|
|
||||||
'</tr>';
|
|
||||||
};
|
|
||||||
socket.onerror = updateState;
|
|
||||||
socket.onmessage = function (event) {
|
|
||||||
commsLog.innerHTML += '<tr>' +
|
|
||||||
'<td class="commslog-server">Server</td>' +
|
|
||||||
'<td class="commslog-client">Client</td>' +
|
|
||||||
'<td class="commslog-data">' + htmlEscape(event.data) + '</td></tr>';
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
function htmlEscape(str) {
|
|
||||||
return str.toString()
|
|
||||||
.replace(/&/g, '&')
|
|
||||||
.replace(/"/g, '"')
|
|
||||||
.replace(/'/g, ''')
|
|
||||||
.replace(/</g, '<')
|
|
||||||
.replace(/>/g, '>');
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
<Project Sdk="Microsoft.NET.Sdk">
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<TargetFramework>net8.0</TargetFramework>
|
<TargetFramework>net7.0</TargetFramework>
|
||||||
<ImplicitUsings>enable</ImplicitUsings>
|
<ImplicitUsings>enable</ImplicitUsings>
|
||||||
<Nullable>enable</Nullable>
|
<Nullable>enable</Nullable>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|||||||
@@ -7,7 +7,7 @@
|
|||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<OutputType>Exe</OutputType>
|
<OutputType>Exe</OutputType>
|
||||||
<TargetFramework>net8.0</TargetFramework>
|
<TargetFramework>net7.0</TargetFramework>
|
||||||
<ImplicitUsings>enable</ImplicitUsings>
|
<ImplicitUsings>enable</ImplicitUsings>
|
||||||
<Nullable>enable</Nullable>
|
<Nullable>enable</Nullable>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
<Project Sdk="Microsoft.NET.Sdk">
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<TargetFramework>net8.0</TargetFramework>
|
<TargetFramework>net7.0</TargetFramework>
|
||||||
<Nullable>enable</Nullable>
|
<Nullable>enable</Nullable>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
@@ -15,7 +15,6 @@
|
|||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="HtmlAgilityPack" Version="1.11.57" />
|
|
||||||
<PackageReference Include="MongoDB.Bson" Version="2.23.1" />
|
<PackageReference Include="MongoDB.Bson" Version="2.23.1" />
|
||||||
<PackageReference Include="MongoDB.Driver" Version="2.23.1" />
|
<PackageReference Include="MongoDB.Driver" Version="2.23.1" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|||||||
@@ -30,8 +30,8 @@ namespace BCG
|
|||||||
{
|
{
|
||||||
using (FileStream fs = new FileStream(fileName, FileMode.Open, FileAccess.Read))
|
using (FileStream fs = new FileStream(fileName, FileMode.Open, FileAccess.Read))
|
||||||
{
|
{
|
||||||
#pragma warning disable SYSLIB0011
|
|
||||||
BinaryFormatter bf = new BinaryFormatter();
|
BinaryFormatter bf = new BinaryFormatter();
|
||||||
|
#pragma warning disable SYSLIB0011
|
||||||
BlueTable bt = (BlueTable)bf.Deserialize(fs);
|
BlueTable bt = (BlueTable)bf.Deserialize(fs);
|
||||||
#pragma warning restore SYSLIB0011
|
#pragma warning restore SYSLIB0011
|
||||||
mySetRealSource(new BlueRowCollectionStaticStream(bt));
|
mySetRealSource(new BlueRowCollectionStaticStream(bt));
|
||||||
|
|||||||
@@ -1,31 +0,0 @@
|
|||||||
namespace BCG {
|
|
||||||
using System.Collections.Generic;
|
|
||||||
|
|
||||||
public class KeyValueToRelationalStream : ViewStream {
|
|
||||||
private readonly ViewStream _kv_str;
|
|
||||||
private readonly string _doc_id_col, _key_col, _val_col;
|
|
||||||
public KeyValueToRelationalStream(ViewStream kv_str, string doc_id_col, string key_col, string val_col) {
|
|
||||||
_kv_str = kv_str;
|
|
||||||
_doc_id_col = doc_id_col;
|
|
||||||
_key_col = key_col;
|
|
||||||
_val_col = val_col;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override void myOnFirstSubscriber() {
|
|
||||||
_kv_str.onChange += _on_str_update;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override void myOnLastSubscriber() {
|
|
||||||
_kv_str.onChange -= _on_str_update;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void _on_str_update(object dp_obj) {
|
|
||||||
DataPacket dp = dp_obj as DataPacket;
|
|
||||||
var rows = dp.newRows;
|
|
||||||
var rv = BlueDataUtility.keyvalue_to_relational<string>(rows, _doc_id_col, _key_col, _val_col);
|
|
||||||
var dp_out = BlueDataUtility.build_dp_inc(rv, myLastValue as DataPacket, true);
|
|
||||||
myFireEvent(dp_out);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,121 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.IO;
|
|
||||||
using System.Security.Cryptography.X509Certificates;
|
|
||||||
|
|
||||||
using BCG;
|
|
||||||
using BCG.Utilities.General;
|
|
||||||
|
|
||||||
using HtmlAgilityPack;
|
|
||||||
|
|
||||||
namespace BCG {
|
|
||||||
class Program {
|
|
||||||
static void Main() {
|
|
||||||
string url = "http://quotes.toscrape.com";
|
|
||||||
List<string> quotes = ScrapeQuotes(url);
|
|
||||||
|
|
||||||
Console.WriteLine("Quotes from http://quotes.toscrape.com:");
|
|
||||||
foreach (var quote in quotes) {
|
|
||||||
Console.WriteLine(quote);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static List<string> ScrapeQuotes(string url) {
|
|
||||||
List<string> quotes = new List<string>();
|
|
||||||
|
|
||||||
// Create a new HtmlWeb instance
|
|
||||||
HtmlWeb web = new HtmlWeb();
|
|
||||||
|
|
||||||
try {
|
|
||||||
// Load the HTML document from the specified URL
|
|
||||||
HtmlDocument document = web.Load(url);
|
|
||||||
|
|
||||||
// Extract quotes from the HTML document
|
|
||||||
var quoteNodes = document.DocumentNode.SelectNodes("//span[@class='text']");
|
|
||||||
if (quoteNodes != null) {
|
|
||||||
foreach (var quoteNode in quoteNodes) {
|
|
||||||
string quote = quoteNode.InnerText.Trim();
|
|
||||||
quotes.Add(quote);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (Exception ex) {
|
|
||||||
Console.WriteLine($"Error: {ex.Message}");
|
|
||||||
}
|
|
||||||
|
|
||||||
return quotes;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public class WebScapperStream : ViewStream, IWorker {
|
|
||||||
private readonly string _url;
|
|
||||||
private readonly string _xpath;
|
|
||||||
private readonly ViewStreamDatabase _vsd;
|
|
||||||
private IDictionary<string, int> _line_id_map = new Dictionary<string, int>();
|
|
||||||
private readonly IBlueTableSchema _schema;
|
|
||||||
|
|
||||||
public WebScapperStream()
|
|
||||||
:
|
|
||||||
this("http://quotes.toscrape.com", "//span[@class='text']", ViewStreamDatabase.instance) { }
|
|
||||||
public WebScapperStream(string url, string xpath)
|
|
||||||
: this(url, xpath, ViewStreamDatabase.instance) { }
|
|
||||||
public WebScapperStream(string url, string xpath, ViewStreamDatabase vsd) {
|
|
||||||
bts.Log.Gen.info($"WebScapperStream cons url: {url} xpath: {xpath}");
|
|
||||||
_schema = BlueDataUtility.form_schema($"{url}_{xpath}",
|
|
||||||
Tuple.Create("id", TypeCode.Int32),
|
|
||||||
Tuple.Create("url", TypeCode.UInt32),
|
|
||||||
Tuple.Create("line", TypeCode.String));
|
|
||||||
_url = url;
|
|
||||||
_xpath = xpath;
|
|
||||||
_vsd = vsd;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override void myOnFirstSubscriber() {
|
|
||||||
MasterWorker.getInstance(false).addWorker(this, TimeSpan.FromSeconds(1));
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override void myOnLastSubscriber() {
|
|
||||||
MasterWorker.getInstance(false).removeWorker(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void doWork() {
|
|
||||||
var lines = scrape_lines(_url, _xpath);
|
|
||||||
Func<string, int> get_id = (line) => {
|
|
||||||
int rv = 0;
|
|
||||||
if (!_line_id_map.TryGetValue(line, out rv))
|
|
||||||
_line_id_map.Add(line, _line_id_map.Count);
|
|
||||||
return rv;
|
|
||||||
};
|
|
||||||
BlueTable nrows = new BlueTable(_schema);
|
|
||||||
foreach (var l in lines) {
|
|
||||||
bts.Log.Gen.info($"line: {l}");
|
|
||||||
var id = get_id(l);
|
|
||||||
BlueRow nr = new BlueRow(_schema, get_id(l), _url, l);
|
|
||||||
nrows.addRow(nr);
|
|
||||||
}
|
|
||||||
var dp = BlueDataUtility.build_dp_inc(nrows, myLastValue as DataPacket, true);
|
|
||||||
myFireEvent(dp);
|
|
||||||
}
|
|
||||||
|
|
||||||
static List<string> scrape_lines(string url, string xpath) {
|
|
||||||
List<string> quotes = new List<string>();
|
|
||||||
// Create a new HtmlWeb instance
|
|
||||||
HtmlWeb web = new HtmlWeb();
|
|
||||||
try {
|
|
||||||
// Load the HTML document from the specified URL
|
|
||||||
HtmlDocument document = web.Load(url);
|
|
||||||
// Extract quotes from the HTML document
|
|
||||||
var quote_nodes = document.DocumentNode.SelectNodes(xpath);
|
|
||||||
if (quote_nodes != null) {
|
|
||||||
foreach (var qn in quote_nodes) {
|
|
||||||
string quote = qn.InnerText.Trim();
|
|
||||||
quotes.Add(quote);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (Exception ex) {
|
|
||||||
Console.WriteLine($"Error: {ex.Message}");
|
|
||||||
}
|
|
||||||
|
|
||||||
return quotes;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,212 +1,37 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
|
||||||
using MongoDB.Driver;
|
using MongoDB.Driver;
|
||||||
using MongoDB.Bson;
|
using MongoDB.Bson;
|
||||||
using BCG;
|
|
||||||
using BCG.Utilities.General;
|
|
||||||
using ZstdSharp.Unsafe;
|
|
||||||
using System.Runtime.CompilerServices;
|
|
||||||
using mbts.spark;
|
|
||||||
|
|
||||||
|
|
||||||
namespace BCG {
|
class Program {
|
||||||
[DataStreamUserRespresentation("mongodb_sql")]
|
static void Main() {
|
||||||
public class MongoDBSQLStream : ViewStream, IWorker {
|
// Set the connection string and database name
|
||||||
private ViewStreamDatabase _vsd;
|
string connectionString = "mongodb://localhost:27017";
|
||||||
private readonly string _connection_string, _db_name, _collection_name;
|
string databaseName = "your_database_name";
|
||||||
private MongoClient _client;
|
string collectionName = "your_collection_name";
|
||||||
private BlueTable _table;
|
|
||||||
|
// Create a MongoClient
|
||||||
|
MongoClient client = new MongoClient(connectionString);
|
||||||
|
|
||||||
// Access the database
|
// Access the database
|
||||||
private IMongoDatabase _database;
|
IMongoDatabase database = client.GetDatabase(databaseName);
|
||||||
private IDictionary<string, int> _id_map = new Dictionary<string, int>();
|
|
||||||
// mongodb://192.168.1.80:27017
|
|
||||||
|
|
||||||
public MongoDBSQLStream(string connection_string, string db_name, string collection_name)
|
// Access the collection
|
||||||
: this(connection_string, db_name, collection_name, ViewStreamDatabase.instance) {
|
IMongoCollection<BsonDocument> collection = database.GetCollection<BsonDocument>(collectionName);
|
||||||
|
|
||||||
}
|
// Fetch all documents from the collection
|
||||||
public MongoDBSQLStream(string connection_string, string db_name, string collection_name, ViewStreamDatabase vsd) {
|
var documents = collection.Find(Builders<BsonDocument>.Filter.Empty).ToCursor();
|
||||||
bts.Log.Gen.info($"MongoDBStream constructed connection_string: {connection_string} " +
|
|
||||||
$"db_name: {db_name}");
|
|
||||||
_connection_string = connection_string;
|
|
||||||
_db_name = db_name;
|
|
||||||
_collection_name = collection_name;
|
|
||||||
_vsd = vsd;
|
|
||||||
_client = new MongoClient(_connection_string);
|
|
||||||
_database = _client.GetDatabase(_db_name);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override void myOnFirstSubscriber() {
|
// Iterate through the documents and print values
|
||||||
MasterWorker.getInstance(false).addWorker(this, TimeSpan.FromSeconds(1));
|
foreach (var document in documents.ToEnumerable()) {
|
||||||
}
|
// Assuming your document structure has fields named "Field1", "Field2", etc.
|
||||||
|
Console.WriteLine("Field1: " + document["Field1"]);
|
||||||
|
Console.WriteLine("Field2: " + document["Field2"]);
|
||||||
|
// Add more fields as needed
|
||||||
|
|
||||||
protected override void myOnLastSubscriber() {
|
// Print a separator between documents
|
||||||
MasterWorker.getInstance(false).removeWorker(this);
|
Console.WriteLine(new string('-', 20));
|
||||||
}
|
|
||||||
|
|
||||||
public void doWork() {
|
|
||||||
_read_mogodb();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void _read_mogodb() {
|
|
||||||
HighPerformanceTimer timer = new HighPerformanceTimer();
|
|
||||||
timer.start();
|
|
||||||
// Access the collection
|
|
||||||
IMongoCollection<BsonDocument> collection = _database.GetCollection<BsonDocument>(name: _collection_name);
|
|
||||||
|
|
||||||
// Fetch all documents from the collection
|
|
||||||
var documents = collection.Find(Builders<BsonDocument>.Filter.Empty).ToCursor();
|
|
||||||
_table = _table == null ? null : new BlueTable(_table.schema);
|
|
||||||
// Iterate through the documents and print values
|
|
||||||
foreach (var document in documents.ToEnumerable()) {
|
|
||||||
bts.Log.Gen.info($"document: {document} json: {document.ToJson()}");
|
|
||||||
if (_table == null) {
|
|
||||||
_init_table(document);
|
|
||||||
}
|
|
||||||
_add_row(document);
|
|
||||||
}
|
|
||||||
timer.stop();
|
|
||||||
bts.Log.Gen.info($"MongoDBStream time to read {_collection_name} {timer.lastIntervalTime} msecs");
|
|
||||||
timer.start();
|
|
||||||
var dp = BlueDataUtility.build_dp_inc(_table, myLastValue == null ? null : myLastValue as DataPacket, true);
|
|
||||||
timer.stop();
|
|
||||||
bts.Log.Gen.info($"MongoDBStream time to compare tables :{timer.lastIntervalTime} msecs");
|
|
||||||
if (dp.addedRows.Count == 0 && dp.deletedRows.Count == 0)
|
|
||||||
return;
|
|
||||||
myFireEvent(dp);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void _init_table(BsonDocument doc_row) {
|
|
||||||
bts.Log.assert(_table == null);
|
|
||||||
List<ColumnInfo> cis = new List<ColumnInfo>();
|
|
||||||
cis.Add((new ColumnInfo("id", 0, typeof(Int32))));
|
|
||||||
foreach (var n in doc_row.Names) {
|
|
||||||
cis.Add(new ColumnInfo(n, cis.Count, typeof(string)));
|
|
||||||
}
|
|
||||||
IBlueTableSchema schema = new BlueTableSchema(cis.ToArray(), _collection_name);
|
|
||||||
_table = new BlueTable(schema);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void _add_row(BsonDocument doc) {
|
|
||||||
var key_val = doc["_id"];
|
|
||||||
var key = key_val.ToString();
|
|
||||||
int id;
|
|
||||||
if (!_id_map.TryGetValue(key, out id)) {
|
|
||||||
id = _id_map.Count;
|
|
||||||
_id_map.Add(key, id);
|
|
||||||
}
|
|
||||||
MutableBlueRow nr = new MutableBlueRow(_table.schema);
|
|
||||||
nr.setValue("id", id);
|
|
||||||
foreach (var n in doc.Names) {
|
|
||||||
var val = doc[n].ToString();
|
|
||||||
nr.setValue(n, val);
|
|
||||||
}
|
|
||||||
_table.addRow(nr);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
[DataStreamUserRespresentation("mongodb")]
|
|
||||||
public class MongoDBStream : ViewStream, IWorker {
|
|
||||||
private ViewStreamDatabase _vsd;
|
|
||||||
private readonly string _connection_string, _db_name, _collection_name;
|
|
||||||
private MongoClient _client;
|
|
||||||
private BlueTable _table;
|
|
||||||
|
|
||||||
// Access the database
|
|
||||||
private IMongoDatabase _database;
|
|
||||||
private IDictionary<string, int> _id_map = new Dictionary<string, int>();
|
|
||||||
// mongodb://192.168.1.80:27017
|
|
||||||
|
|
||||||
public MongoDBStream(string connection_string, string db_name, string collection_name)
|
|
||||||
: this(connection_string, db_name, collection_name, ViewStreamDatabase.instance) {
|
|
||||||
|
|
||||||
}
|
|
||||||
public MongoDBStream(string connection_string, string db_name, string collection_name, ViewStreamDatabase vsd) {
|
|
||||||
bts.Log.Gen.info($"MongoDBStream constructed connection_string: {connection_string} " +
|
|
||||||
$"db_name: {db_name}");
|
|
||||||
_connection_string = connection_string;
|
|
||||||
_db_name = db_name;
|
|
||||||
_collection_name = collection_name;
|
|
||||||
_vsd = vsd;
|
|
||||||
_client = new MongoClient(_connection_string);
|
|
||||||
_database = _client.GetDatabase(_db_name);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override void myOnFirstSubscriber() {
|
|
||||||
MasterWorker.getInstance(false).addWorker(this, TimeSpan.FromSeconds(1));
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override void myOnLastSubscriber() {
|
|
||||||
MasterWorker.getInstance(false).removeWorker(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void doWork() {
|
|
||||||
_read_mogodb();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void _read_mogodb() {
|
|
||||||
HighPerformanceTimer timer = new HighPerformanceTimer();
|
|
||||||
timer.start();
|
|
||||||
// Access the collection
|
|
||||||
IMongoCollection<BsonDocument> collection = _database.GetCollection<BsonDocument>(name: _collection_name);
|
|
||||||
|
|
||||||
// Fetch all documents from the collection
|
|
||||||
var documents = collection.Find(Builders<BsonDocument>.Filter.Empty).ToCursor();
|
|
||||||
_table = _table == null ? null : new BlueTable(_table.schema);
|
|
||||||
// Iterate through the documents and print values
|
|
||||||
foreach (var document in documents.ToEnumerable()) {
|
|
||||||
bts.Log.Gen.info($"document: {document} json: {document.ToJson()}");
|
|
||||||
if (_table == null) {
|
|
||||||
_init_table(document);
|
|
||||||
}
|
|
||||||
_add_doc(document);
|
|
||||||
}
|
|
||||||
timer.stop();
|
|
||||||
bts.Log.Gen.info($"MongoDBStream time to read {_collection_name} {timer.lastIntervalTime} msecs");
|
|
||||||
timer.start();
|
|
||||||
var dp = BlueDataUtility.build_dp_inc(_table, myLastValue == null ? null : myLastValue as DataPacket, true);
|
|
||||||
timer.stop();
|
|
||||||
bts.Log.Gen.info($"MongoDBStream time to compare tables :{timer.lastIntervalTime} msecs");
|
|
||||||
if (dp.addedRows.Count == 0 && dp.deletedRows.Count == 0)
|
|
||||||
return;
|
|
||||||
myFireEvent(dp);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void _init_table(BsonDocument doc_row) {
|
|
||||||
bts.Log.assert(_table == null);
|
|
||||||
var schema = BlueDataUtility.form_schema($"{_database}_{_table}",
|
|
||||||
Tuple.Create("id", TypeCode.Int32),
|
|
||||||
Tuple.Create("doc_id", TypeCode.String),
|
|
||||||
Tuple.Create("key", TypeCode.String),
|
|
||||||
Tuple.Create("value", TypeCode.String));
|
|
||||||
_table = new BlueTable(schema);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void _add_doc(BsonDocument doc) {
|
|
||||||
var doc_id_bson = doc["_id"];
|
|
||||||
var doc_id = doc_id_bson.ToString();
|
|
||||||
Func<string, string, int> get_id = (string doc_id, string key) => {
|
|
||||||
string hash_key = $"{doc_id}_{key}";
|
|
||||||
int id = 0;
|
|
||||||
if (!_id_map.TryGetValue(hash_key, out id)) {
|
|
||||||
id = _id_map.Count;
|
|
||||||
_id_map.Add(hash_key, id);
|
|
||||||
}
|
|
||||||
return id;
|
|
||||||
};
|
|
||||||
foreach (var n in doc.Names) {
|
|
||||||
string key = n;
|
|
||||||
var id = get_id(doc_id, key);
|
|
||||||
MutableBlueRow nr = new MutableBlueRow(_table.schema);
|
|
||||||
var val = doc[n].ToString();
|
|
||||||
nr.setValue("id", id);
|
|
||||||
nr.setValue("doc_id", doc_id);
|
|
||||||
nr.setValue("key", key);
|
|
||||||
nr.setValue("value", val);
|
|
||||||
_table.addRow(nr);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
File diff suppressed because it is too large.
Load diff
@@ -1,103 +1,95 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Text.Json.Serialization;
|
|
||||||
|
namespace BCG
|
||||||
namespace BCG
|
{
|
||||||
{
|
/// <summary>
|
||||||
/// <summary>
|
/// Resprsents information about a column schema, ie its ordinal, the type, the name and constant value or expression. where applicable
|
||||||
/// Resprsents information about a column schema, ie its ordinal, the type, the name and constant value or expression. where applicable
|
/// </summary>
|
||||||
/// </summary>
|
[Serializable]
|
||||||
[Serializable]
|
public class ColumnInfo : IComparable
|
||||||
public class ColumnInfo : IComparable
|
{
|
||||||
{
|
public static bool isAllowedType (Type t)
|
||||||
public static bool isAllowedType (Type t)
|
{
|
||||||
{
|
return
|
||||||
return
|
t.Equals (typeof (System.Int32)) ||
|
||||||
t.Equals (typeof (System.Int32)) ||
|
t.Equals (typeof (System.Int64)) ||
|
||||||
t.Equals (typeof (System.Int64)) ||
|
t.Equals (typeof (System.Double)) ||
|
||||||
t.Equals (typeof (System.Double)) ||
|
t.Equals (typeof (System.String)) ||
|
||||||
t.Equals (typeof (System.String)) ||
|
t.Equals (typeof (System.DateTime)) ? true :
|
||||||
t.Equals (typeof (System.DateTime)) ? true :
|
false;
|
||||||
false;
|
}
|
||||||
}
|
|
||||||
|
//the default column info does not cach values in a lookup table, therefore it is in group 0
|
||||||
//the default column info does not cach values in a lookup table, therefore it is in group 0
|
public ColumnInfo(string colName, int colNo, Type colType)
|
||||||
public ColumnInfo(string colName, int colNo, Type colType)
|
: this(colName, colNo, colType, 0)
|
||||||
: this(colName, colNo, colType, 0)
|
{
|
||||||
{
|
|
||||||
|
|
||||||
|
}
|
||||||
}
|
|
||||||
|
public ColumnInfo(string colName, int colNo, Type colType, short colGroup)
|
||||||
public ColumnInfo(string colName, int colNo, Type colType, short colGroup)
|
{
|
||||||
{
|
myColName = colName;
|
||||||
myColName = colName;
|
myColNo = colNo;
|
||||||
myColNo = colNo;
|
myColType = colType;
|
||||||
myColType = colType;
|
myLookupTableGroup = colGroup;
|
||||||
myLookupTableGroup = colGroup;
|
myColValue = null;
|
||||||
myColValue = null;
|
}
|
||||||
}
|
|
||||||
|
|
||||||
|
public ColumnInfo (string colName, int colNo, object colValue)
|
||||||
public ColumnInfo (string colName, int colNo, object colValue)
|
: this (colName, colNo, colValue.GetType()) {myColValue = colValue == null ? DBNull.Value : colValue;}
|
||||||
: this (colName, colNo, colValue.GetType()) {myColValue = colValue == null ? DBNull.Value : colValue;}
|
|
||||||
|
public ColumnInfo (string colName, int colNo, Type colType, object colValue)
|
||||||
public ColumnInfo (string colName, int colNo, Type colType, object colValue)
|
: this (colName, colNo, colType)
|
||||||
: this (colName, colNo, colType)
|
{
|
||||||
{
|
|
||||||
|
myColValue = colValue == null ? DBNull.Value : colValue;
|
||||||
myColValue = colValue == null ? DBNull.Value : colValue;
|
|
||||||
|
}
|
||||||
}
|
|
||||||
|
|
||||||
|
public int CompareTo (object obj)
|
||||||
public int CompareTo (object obj)
|
{
|
||||||
{
|
int otherColNo = ((ColumnInfo)(obj)).colNo;
|
||||||
int otherColNo = ((ColumnInfo)(obj)).colNo;
|
if (myColNo < otherColNo)
|
||||||
if (myColNo < otherColNo)
|
return -1;
|
||||||
return -1;
|
else if (myColNo > otherColNo)
|
||||||
else if (myColNo > otherColNo)
|
return 1;
|
||||||
return 1;
|
return 0;
|
||||||
return 0;
|
}
|
||||||
}
|
|
||||||
|
//public bool isCalculated{get{return myExpression != null;}}
|
||||||
//public bool isCalculated{get{return myExpression != null;}}
|
|
||||||
|
public bool isConstant {get{return myColValue != null;}}
|
||||||
public bool isConstant {get{return myColValue != null;}}
|
|
||||||
|
private int myColNo;
|
||||||
private int myColNo;
|
private string myColName;
|
||||||
private string myColName;
|
private Type myColType;
|
||||||
private Type myColType;
|
//private string myExpression;
|
||||||
//private string myExpression;
|
private object myColValue;
|
||||||
private object myColValue;
|
|
||||||
|
//no table will ever have more than 65,536 columns
|
||||||
//no table will ever have more than 65,536 columns
|
//if it does, we are in serious trouble
|
||||||
//if it does, we are in serious trouble
|
private short myLookupTableGroup;
|
||||||
private short myLookupTableGroup;
|
|
||||||
|
public int colNo{get{return myColNo;}}
|
||||||
[JsonPropertyName("colNo")]
|
public string colName {get{return myColName;}}
|
||||||
public int colNo{get{return myColNo;}}
|
public Type colType {get{return myColType;}}
|
||||||
|
|
||||||
[JsonPropertyName("colName")]
|
|
||||||
public string colName {get{return myColName;}}
|
//this value corresponds to how the data in the column is serialized.
|
||||||
|
//if the group number is 0, the values are not stored in a table
|
||||||
[JsonPropertyName("colType")]
|
//if the vlaue is non-zero, all columns with the the same lookup table group are stored in the same lookup table together
|
||||||
public Type colType {get{return myColType;}}
|
//are stored together in a lookup table
|
||||||
|
|
||||||
|
|
||||||
//this value corresponds to how the data in the column is serialized.
|
public short lookupTableGroup
|
||||||
//if the group number is 0, the values are not stored in a table
|
{
|
||||||
//if the vlaue is non-zero, all columns with the the same lookup table group are stored in the same lookup table together
|
get { return myLookupTableGroup; }
|
||||||
//are stored together in a lookup table
|
set { myLookupTableGroup = value; }
|
||||||
|
}
|
||||||
|
//public string expression {get{return myExpression;}}
|
||||||
[JsonIgnore]
|
public object colValue {get{return myColValue;}}
|
||||||
public short lookupTableGroup
|
}
|
||||||
{
|
}
|
||||||
get { return myLookupTableGroup; }
|
|
||||||
set { myLookupTableGroup = value; }
|
|
||||||
}
|
|
||||||
//public string expression {get{return myExpression;}}
|
|
||||||
[JsonIgnore]
|
|
||||||
public object colValue {get{return myColValue;}}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -3,7 +3,8 @@ using System.Collections.Generic;
|
|||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
|
|
||||||
namespace BCG {
|
namespace BCG
|
||||||
|
{
|
||||||
/*
|
/*
|
||||||
These fields are first going to be used by BlueRay to help it determine what differnet tags mean, but eventually
|
These fields are first going to be used by BlueRay to help it determine what differnet tags mean, but eventually
|
||||||
* the goal is that they will be used for all services in this system.
|
* the goal is that they will be used for all services in this system.
|
||||||
@@ -11,12 +12,11 @@ namespace BCG {
|
|||||||
* not all metadata fields are present in all metadata; it depends upon the class. For example, a bluepages service
|
* not all metadata fields are present in all metadata; it depends upon the class. For example, a bluepages service
|
||||||
* probably won't have a 'symbol' or 'exchange' field populated, but it'll probably have the 'type' of service populated
|
* probably won't have a 'symbol' or 'exchange' field populated, but it'll probably have the 'type' of service populated
|
||||||
*/
|
*/
|
||||||
public enum MetaDataField {
|
public enum MetaDataField { Protocol, //is this a bluepages service, a pubsub tag, a blueray tag, a 29 west thing, or what
|
||||||
Protocol, // is this a bluepages service, a pubsub tag, a blueray tag, a 29 west thing, or what
|
Type, //if it's a multicast, is it option, fireprice, whatever. If it's bluepages, is it persistence, pubsub, whatever
|
||||||
Type, // if it's a multicast, is it option, fireprice, whatever. If it's bluepages, is it persistence, pubsub, whatever
|
Symbol,
|
||||||
Symbol,
|
Exchange,
|
||||||
Exchange,
|
Platform,
|
||||||
MarketDataType, // BAM (bid, ask, mid), last, etc
|
MarketDataType, // BAM (bid, ask, mid), last, etc
|
||||||
RealName
|
RealName }; //does this thing have a better name we could call it
|
||||||
}; //does this thing have a better name we could call it
|
|
||||||
}
|
}
|
||||||
@@ -1,61 +0,0 @@
|
|||||||
#nullable enable
|
|
||||||
using BCG;
|
|
||||||
|
|
||||||
using System;
|
|
||||||
using System.Text.Json;
|
|
||||||
using System.Text.Json.Serialization;
|
|
||||||
|
|
||||||
namespace Bts.DataStreamCore;
|
|
||||||
|
|
||||||
class BlueRowJsonConverter : JsonConverter<IBlueRow> {
|
|
||||||
IBlueTableSchema? schema = null;
|
|
||||||
|
|
||||||
public override IBlueRow Read(
|
|
||||||
ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) {
|
|
||||||
if (schema is null) {
|
|
||||||
throw new InvalidOperationException("Cannot read row from json because schema is null");
|
|
||||||
}
|
|
||||||
var mbr = new MutableBlueRow(schema);
|
|
||||||
int colNum = 0;
|
|
||||||
switch (reader.TokenType) {
|
|
||||||
case JsonTokenType.StartArray:
|
|
||||||
while (reader.Read() && reader.TokenType != JsonTokenType.EndArray) {
|
|
||||||
mbr.setValue(colNum++, readCellValue(ref reader));
|
|
||||||
colNum++;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case JsonTokenType.StartObject:
|
|
||||||
while (reader.Read() && reader.TokenType != JsonTokenType.EndObject) {
|
|
||||||
if (reader.TokenType != JsonTokenType.PropertyName) {
|
|
||||||
throw new InvalidOperationException(
|
|
||||||
"Expected 'PropertyName' immediately after 'StartObject' token in row json");
|
|
||||||
}
|
|
||||||
var colName = reader.GetString();
|
|
||||||
if (!reader.Read()) {
|
|
||||||
throw new InvalidOperationException(
|
|
||||||
"Failed to read token immediately after 'PropertyName' token in row json");
|
|
||||||
}
|
|
||||||
mbr.setValue(colName, readCellValue(ref reader));
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
if (mbr.Count != schema.columnInfo.Length) {
|
|
||||||
throw new InvalidOperationException(
|
|
||||||
$"Row json array length '{mbr.Count}' does not match the schema's " +
|
|
||||||
$"column length '{schema.columnInfo.Length}'.");
|
|
||||||
}
|
|
||||||
return mbr;
|
|
||||||
|
|
||||||
static object? readCellValue(ref Utf8JsonReader reader) =>
|
|
||||||
reader.TokenType switch {
|
|
||||||
JsonTokenType.String => reader.GetString(),
|
|
||||||
JsonTokenType.Number => reader.GetDouble(),
|
|
||||||
JsonTokenType.True or JsonTokenType.False => reader.GetBoolean(),
|
|
||||||
_ => throw new InvalidOperationException("Failed to read cell value"),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
public override void Write(Utf8JsonWriter writer, IBlueRow value, JsonSerializerOptions options) {
|
|
||||||
throw new NotImplementedException();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,154 +0,0 @@
|
|||||||
#nullable enable
|
|
||||||
using BCG;
|
|
||||||
|
|
||||||
using Spark.Core.Json;
|
|
||||||
|
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Text.Json;
|
|
||||||
using System.Text.Json.Serialization;
|
|
||||||
|
|
||||||
namespace Bts.DataStreamCore;
|
|
||||||
|
|
||||||
class DataPacketJsonConverter : JsonConverter<DataPacket> {
|
|
||||||
/// <summary>
|
|
||||||
/// Whether or not to include a field for current rows (i.e., snapshot of current state). By
|
|
||||||
/// default, only incremental changes are included ('added' and 'deleted').
|
|
||||||
/// </summary>
|
|
||||||
public bool IncludeCurrent { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Whether to treat rows as objects (name/value pairs), or arrays (list of values). Arrays
|
|
||||||
/// result in smaller payloads, but aren't as easy to read as name/value pairs (for mere humans).
|
|
||||||
/// </summary>
|
|
||||||
public bool UseObjectRows { get; set; }
|
|
||||||
|
|
||||||
public override DataPacket? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) {
|
|
||||||
if (!reader.ReadObject()) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
if (!reader.ReadPropertyName(out var prop)) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
if (!reader.Read()) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
if (prop == "added") {
|
|
||||||
var added = new List<MutableBlueRow>();
|
|
||||||
if (reader.TokenType != JsonTokenType.StartArray) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
while (true) {
|
|
||||||
if (!reader.Read()) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
if (reader.TokenType != JsonTokenType.StartArray || !reader.Read()) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
switch (reader.TokenType) {
|
|
||||||
case JsonTokenType.String:
|
|
||||||
// DAMNIT! Need the schema.
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public override void Write(Utf8JsonWriter writer, DataPacket value, JsonSerializerOptions options) {
|
|
||||||
using var _ = writer.WriteObject();
|
|
||||||
using (writer.WriteArray("schema")) {
|
|
||||||
foreach (var col in value.schema.columnInfo) {
|
|
||||||
using (writer.WriteObject()) {
|
|
||||||
writer.WriteString("name", col.colName);
|
|
||||||
writer.WriteNumber("index", col.colNo);
|
|
||||||
// TODO: use language agnostic type names (e.g., 'string', 'integer', 'float', 'date' etc.)
|
|
||||||
writer.WriteString("type", col.colType.ToString());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var cellWriters = value.schema.columnInfo.Select(getCellWriter).ToList();
|
|
||||||
using (writer.WriteArray("added")) {
|
|
||||||
foreach (IBlueRow row in value.addedRows) {
|
|
||||||
using (writer.WriteArray()) {
|
|
||||||
for (int i = 0; i < cellWriters.Count; i++) {
|
|
||||||
cellWriters[i](row[i]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
using (writer.WriteArray("deleted")) {
|
|
||||||
foreach (IBlueRow row in value.deletedRows) {
|
|
||||||
using (writer.WriteArray()) {
|
|
||||||
for (int i = 0; i < cellWriters.Count; i++) {
|
|
||||||
cellWriters[i](row[i]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (IncludeCurrent) {
|
|
||||||
using (writer.WriteArray("current")) {
|
|
||||||
foreach (IBlueRow row in value.newRows) {
|
|
||||||
using (writer.WriteArray()) {
|
|
||||||
for (int i = 0; i < cellWriters.Count; i++) {
|
|
||||||
cellWriters[i](row[i]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Action<object?> getCellWriter(ColumnInfo colInfo) {
|
|
||||||
var writeVal = getCellValueWriter(colInfo);
|
|
||||||
if (!UseObjectRows) {
|
|
||||||
return writeVal;
|
|
||||||
}
|
|
||||||
return v => {
|
|
||||||
writer.WritePropertyName(colInfo.colName);
|
|
||||||
writeVal(v);
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
Action<object?> getCellValueWriter(ColumnInfo colInfo) {
|
|
||||||
var t = colInfo.colType;
|
|
||||||
if (t == typeof(int)) {
|
|
||||||
return nullCheckWrapper(v => writer.WriteNumberValue((int)v));
|
|
||||||
}
|
|
||||||
if (t == typeof(uint)) {
|
|
||||||
return nullCheckWrapper(v => writer.WriteNumberValue((uint)v));
|
|
||||||
}
|
|
||||||
if (t == typeof(long)) {
|
|
||||||
return nullCheckWrapper(v => writer.WriteNumberValue((long)v));
|
|
||||||
}
|
|
||||||
if (t == typeof(ulong)) {
|
|
||||||
return nullCheckWrapper(v => writer.WriteNumberValue((ulong)v));
|
|
||||||
}
|
|
||||||
if (t == typeof(short)) {
|
|
||||||
return nullCheckWrapper(v => writer.WriteNumberValue((short)v));
|
|
||||||
}
|
|
||||||
if (t == typeof(ushort)) {
|
|
||||||
return nullCheckWrapper(v => writer.WriteNumberValue((ushort)v));
|
|
||||||
}
|
|
||||||
if (t == typeof(double)) {
|
|
||||||
return nullCheckWrapper(v => writer.WriteNumberValue((double)v));
|
|
||||||
}
|
|
||||||
if (t == typeof(float)) {
|
|
||||||
return nullCheckWrapper(v => writer.WriteNumberValue((float)v));
|
|
||||||
}
|
|
||||||
if (t == typeof(bool)) {
|
|
||||||
return nullCheckWrapper(v => writer.WriteBooleanValue((bool)v));
|
|
||||||
}
|
|
||||||
return nullCheckWrapper(v => writer.WriteStringValue(v?.ToString()));
|
|
||||||
|
|
||||||
Action<object?> nullCheckWrapper(Action<object> writeNonNull) =>
|
|
||||||
v => {
|
|
||||||
if (v is null) {
|
|
||||||
writer.WriteNullValue();
|
|
||||||
} else {
|
|
||||||
writeNonNull(v);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
||||||
<TargetFramework>net8.0</TargetFramework>
|
<TargetFramework>net7.0</TargetFramework>
|
||||||
<Nullable>disable</Nullable>
|
<Nullable>disable</Nullable>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
<Project Sdk="Microsoft.NET.Sdk">
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<TargetFramework>net8.0</TargetFramework>
|
<TargetFramework>net7.0</TargetFramework>
|
||||||
<ImplicitUsings>enable</ImplicitUsings>
|
<ImplicitUsings>enable</ImplicitUsings>
|
||||||
<Nullable>enable</Nullable>
|
<Nullable>enable</Nullable>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|||||||
@@ -11,7 +11,7 @@
|
|||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<OutputType>Exe</OutputType>
|
<OutputType>Exe</OutputType>
|
||||||
<TargetFramework>net8.0</TargetFramework>
|
<TargetFramework>net7.0</TargetFramework>
|
||||||
<Nullable>enable</Nullable>
|
<Nullable>enable</Nullable>
|
||||||
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ using System.Threading;
|
|||||||
|
|
||||||
bts.Log.Gen.info($"Starting DSPublisher");
|
bts.Log.Gen.info($"Starting DSPublisher");
|
||||||
Console.WriteLine("Starting DSPublisher...");
|
Console.WriteLine("Starting DSPublisher...");
|
||||||
bool run_echo_tcp_client_only = false;
|
bool run_echo_tcp_client_only = true;
|
||||||
static void init(string[] args) {
|
static void init(string[] args) {
|
||||||
Func<string, string, Task> login = async (l, p) => {
|
Func<string, string, Task> login = async (l, p) => {
|
||||||
var login = l;
|
var login = l;
|
||||||
@@ -31,7 +31,6 @@ else
|
|||||||
|
|
||||||
static ViewStream build_stream(string stream_expression) {
|
static ViewStream build_stream(string stream_expression) {
|
||||||
try {
|
try {
|
||||||
bts.Log.Gen.info($"build_stream stream_expression: {stream_expression}");
|
|
||||||
var exp = DataStreamExpressionsInterpreter.Evaluate(stream_expression);
|
var exp = DataStreamExpressionsInterpreter.Evaluate(stream_expression);
|
||||||
var rv = exp.getView(new Dictionary<string, StatementExpression>(0));
|
var rv = exp.getView(new Dictionary<string, StatementExpression>(0));
|
||||||
return rv;
|
return rv;
|
||||||
@@ -59,16 +58,14 @@ void _publish_streams() {
|
|||||||
CloudStreamManager csm = new CloudStreamManager();
|
CloudStreamManager csm = new CloudStreamManager();
|
||||||
// get it from config
|
// get it from config
|
||||||
Dictionary<string, string> streams_to_publish = new Dictionary<string, string>() {
|
Dictionary<string, string> streams_to_publish = new Dictionary<string, string>() {
|
||||||
//{"bbo","bbo()"},
|
{"bbo","bbo()"},
|
||||||
//{"instruments","instruments()"},
|
{"instruments","instruments()"},
|
||||||
//{"timesales","timesales(10000)"},
|
//{"timesales","timesales(10000)"},
|
||||||
//{"books","books()"},
|
{"books","books()"},
|
||||||
{"seconds_1_10","second(1,10)"},
|
// {"seconds_1_10","second(1,10)"},
|
||||||
{"literal_420","literal(\"x\", \"420.0\")"},
|
{"literal_420","literal(\"x\", \"420.0\")"},
|
||||||
{"mongodb_testcollection", "mongodb(\"mongodb://192.168.1.80:27017\",\"local\",\"test_collection\")"}
|
// {"bbo_ts","timeSeries(bbo())"}
|
||||||
};
|
};
|
||||||
// {"bbo_ts","timeSeries(bbo())"}
|
|
||||||
|
|
||||||
foreach (var kv in streams_to_publish) {
|
foreach (var kv in streams_to_publish) {
|
||||||
var name = kv.Key;
|
var name = kv.Key;
|
||||||
var exp = kv.Value;
|
var exp = kv.Value;
|
||||||
@@ -80,7 +77,6 @@ void _publish_streams() {
|
|||||||
System.Threading.Thread.Sleep(TimeSpan.FromSeconds(1));
|
System.Threading.Thread.Sleep(TimeSpan.FromSeconds(1));
|
||||||
ViewStreamDatabase.instance.doWork();
|
ViewStreamDatabase.instance.doWork();
|
||||||
csm.doWork();
|
csm.doWork();
|
||||||
MasterWorker.getInstance(false).doWork();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<OutputType>Exe</OutputType>
|
<OutputType>Exe</OutputType>
|
||||||
<TargetFramework>net8.0-windows</TargetFramework>
|
<TargetFramework>net7.0-windows</TargetFramework>
|
||||||
<Nullable>enable</Nullable>
|
<Nullable>enable</Nullable>
|
||||||
<UseWindowsForms>true</UseWindowsForms>
|
<UseWindowsForms>true</UseWindowsForms>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|||||||
@@ -38,8 +38,6 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DataStreamTests", "DataStre
|
|||||||
EndProject
|
EndProject
|
||||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SharedStreams", "SharedStreams\SharedStreams.csproj", "{C1EBC423-4668-43D4-B7E5-A291086BA823}"
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SharedStreams", "SharedStreams\SharedStreams.csproj", "{C1EBC423-4668-43D4-B7E5-A291086BA823}"
|
||||||
EndProject
|
EndProject
|
||||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Bts.DataStreamWebApp.Server", "Bts.DataStreamWebApp.Server\Bts.DataStreamWebApp.Server.csproj", "{6DE5E4CE-423E-4571-81F7-539D268D6059}"
|
|
||||||
EndProject
|
|
||||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Bts.DataStream.Websocket", "Bts.DataStream.Websocket\Bts.DataStream.Websocket.csproj", "{088655DA-3126-4F8A-9AC7-9061FDA891FF}"
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Bts.DataStream.Websocket", "Bts.DataStream.Websocket\Bts.DataStream.Websocket.csproj", "{088655DA-3126-4F8A-9AC7-9061FDA891FF}"
|
||||||
EndProject
|
EndProject
|
||||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Bts.DataStream.WebsocketServer", "Bts.DataStream.WebsocketServer\Bts.DataStream.WebsocketServer.csproj", "{35823BE0-C883-476A-8B30-D6097CBA1A8D}"
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Bts.DataStream.WebsocketServer", "Bts.DataStream.WebsocketServer\Bts.DataStream.WebsocketServer.csproj", "{35823BE0-C883-476A-8B30-D6097CBA1A8D}"
|
||||||
@@ -133,10 +131,6 @@ Global
|
|||||||
{C1EBC423-4668-43D4-B7E5-A291086BA823}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
{C1EBC423-4668-43D4-B7E5-A291086BA823}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
{C1EBC423-4668-43D4-B7E5-A291086BA823}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
{C1EBC423-4668-43D4-B7E5-A291086BA823}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
{C1EBC423-4668-43D4-B7E5-A291086BA823}.Release|Any CPU.Build.0 = Release|Any CPU
|
{C1EBC423-4668-43D4-B7E5-A291086BA823}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
{6DE5E4CE-423E-4571-81F7-539D268D6059}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
|
||||||
{6DE5E4CE-423E-4571-81F7-539D268D6059}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
|
||||||
{6DE5E4CE-423E-4571-81F7-539D268D6059}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
|
||||||
{6DE5E4CE-423E-4571-81F7-539D268D6059}.Release|Any CPU.Build.0 = Release|Any CPU
|
|
||||||
{088655DA-3126-4F8A-9AC7-9061FDA891FF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
{088655DA-3126-4F8A-9AC7-9061FDA891FF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
{088655DA-3126-4F8A-9AC7-9061FDA891FF}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
{088655DA-3126-4F8A-9AC7-9061FDA891FF}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
{088655DA-3126-4F8A-9AC7-9061FDA891FF}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
{088655DA-3126-4F8A-9AC7-9061FDA891FF}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
<Project Sdk="Microsoft.NET.Sdk">
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<TargetFramework>net8.0</TargetFramework>
|
<TargetFramework>net7.0</TargetFramework>
|
||||||
<ImplicitUsings>enable</ImplicitUsings>
|
<ImplicitUsings>enable</ImplicitUsings>
|
||||||
<Nullable>enable</Nullable>
|
<Nullable>enable</Nullable>
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
<Project Sdk="Microsoft.NET.Sdk">
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<TargetFramework>net8.0</TargetFramework>
|
<TargetFramework>net7.0</TargetFramework>
|
||||||
<ImplicitUsings>enable</ImplicitUsings>
|
<ImplicitUsings>enable</ImplicitUsings>
|
||||||
<Nullable>enable</Nullable>
|
<Nullable>enable</Nullable>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|||||||
@@ -6,7 +6,7 @@
|
|||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<OutputType>Exe</OutputType>
|
<OutputType>Exe</OutputType>
|
||||||
<TargetFramework>net8.0</TargetFramework>
|
<TargetFramework>net7.0</TargetFramework>
|
||||||
<ImplicitUsings>enable</ImplicitUsings>
|
<ImplicitUsings>enable</ImplicitUsings>
|
||||||
<Nullable>enable</Nullable>
|
<Nullable>enable</Nullable>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
<Project Sdk="Microsoft.NET.Sdk">
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<TargetFramework>net8.0</TargetFramework>
|
<TargetFramework>net7.0</TargetFramework>
|
||||||
<ImplicitUsings>enable</ImplicitUsings>
|
<ImplicitUsings>enable</ImplicitUsings>
|
||||||
<Nullable>enable</Nullable>
|
<Nullable>enable</Nullable>
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
<Project Sdk="Microsoft.NET.Sdk">
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<TargetFramework>net8.0-windows</TargetFramework>
|
<TargetFramework>net7.0-windows</TargetFramework>
|
||||||
<ImplicitUsings>enable</ImplicitUsings>
|
<ImplicitUsings>enable</ImplicitUsings>
|
||||||
<Nullable>enable</Nullable>
|
<Nullable>enable</Nullable>
|
||||||
<UseWindowsForms>true</UseWindowsForms>
|
<UseWindowsForms>true</UseWindowsForms>
|
||||||
|
|||||||
@@ -47,30 +47,30 @@ namespace BCG {
|
|||||||
/// of Int32.Max would return all the messages it has
|
/// of Int32.Max would return all the messages it has
|
||||||
/// received between last getUpdates and this call.
|
/// received between last getUpdates and this call.
|
||||||
/// </summary
|
/// </summary
|
||||||
IList<byte[]> get_updates(TimeSpan timeout, int maxElements);
|
IList<byte[]> getUpdates(TimeSpan timeout, int maxElements);
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// server URL to which the connection is made
|
/// server URL to which the connection is made
|
||||||
/// </summary>
|
/// </summary>
|
||||||
string server_url { get; }
|
string serverURL { get; }
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// client URL as seen by the server. if a connection has not
|
/// client URL as seen by the server. if a connection has not
|
||||||
/// been established (as can happen in TCP) an empty string
|
/// been established (as can happen in TCP) an empty string
|
||||||
/// is returned
|
/// is returned
|
||||||
/// </summary>
|
/// </summary>
|
||||||
string client_url { get; }
|
string clientURL { get; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// registers the callback to receive
|
/// registers the callback to receive
|
||||||
/// connection notifications
|
/// connection notifications
|
||||||
/// </summary>
|
/// </summary>
|
||||||
void register_connection_callback(PubSubServerConnectionStatusChange del);
|
void registerCallback(ConnectionStatusChange del);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// registers callbacks to reveive data
|
/// registers callbacks to reveive data
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="del"></param>
|
/// <param name="del"></param>
|
||||||
void register_data_callback(PubSubServerDataDelegate del);
|
void registerDataCallback(DataDelegate del);
|
||||||
|
|
||||||
void register_send_confirmation_callback(SendConfirmationDelegate del);
|
void registerSendConfirmationCallback(SendConfirmationDelegate del);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
+80
-28
@@ -8,7 +8,6 @@ using System.IO;
|
|||||||
//using BCG.Utilities.General;
|
//using BCG.Utilities.General;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using mbts;
|
using mbts;
|
||||||
using bts;
|
|
||||||
|
|
||||||
namespace BCG {
|
namespace BCG {
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -21,27 +20,26 @@ namespace BCG {
|
|||||||
/// ITCPConnectionHandler.onConnection will be called on any arbitrary thread
|
/// ITCPConnectionHandler.onConnection will be called on any arbitrary thread
|
||||||
///
|
///
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class PubSubServerManagedTCPConnection : IPubSubServerConnection,// external facing interface to communicate msgs to Pubsubserver
|
public class PubSubServerManagedTCPConnection : IPubSubServerConnection,
|
||||||
ITCPMessageHandler, // to get messages from ITCPCOnnection
|
ITCPMessageHandler,
|
||||||
ITCPConnectionHandler { // to get connections from AsyncTCPConnector_APM
|
ITCPConnectionHandler {
|
||||||
private ITCPConnection _connection, _new_connection;
|
private ITCPConnection _connection, _new_connection;
|
||||||
private readonly BlutoSubAddress _server_address;
|
private readonly BlutoSubAddress _server_address;
|
||||||
// if a message(with confirm) can not be sent , its id is queued here,
|
// if a message(with confirm) can not be sent , its id is queued here,
|
||||||
// on reconnection, _on_send_onfirm_callback is fired with a negative on these ids, so the user
|
// on reconnection, _on_send_onfirm_callback is fired with a negative on these ids, so the user
|
||||||
//can re-send it. The re-send mechanism is not in implemented in this class, but by the user of this class
|
//can re-send it. The re-send mechanism is not in implemented in this class, but by the user of this class
|
||||||
private ThreadSafeQueue<string> _negative_unsent_confs = new ThreadSafeQueue<string>();
|
private ThreadSafeQueue<string> _negative_unsent_confs = new ThreadSafeQueue<string>();
|
||||||
// in onData, data is queued here, for polling
|
// in onData, data is queued here
|
||||||
private SyncQueue<byte[]> _in_data_queue = new SyncQueue<byte[]>();
|
private SyncQueue<byte[]> _in_data_queue = new SyncQueue<byte[]>();
|
||||||
private DateTime _last_hb = DateTime.Now;
|
private DateTime _last_hb = DateTime.Now;
|
||||||
private PubSubServerConnectionStatusChange _conn_callback;
|
private ConnectionStatusChange _conn_callback;
|
||||||
private PubSubServerDataDelegate _data_callback;
|
private DataDelegate _data_callback;
|
||||||
private SendConfirmationDelegate _on_send_confirm_callback;
|
private SendConfirmationDelegate _on_send_onfirm_callback;
|
||||||
private readonly TimeSpan _connection_drop_threshold =
|
private readonly TimeSpan _connection_drop_threshold =
|
||||||
new TimeSpan(0, 0, 0, 0,
|
new TimeSpan(0, 0, 0, 0, PubSub3Config.instance.PS3_HeartBeatInterval * PubSub3Config.instance.PS3_MissingHeartBeatsForGC);
|
||||||
PubSub3Config.instance.PS3_HeartBeatInterval * PubSub3Config.instance.PS3_MissingHeartBeatsForGC);
|
|
||||||
|
|
||||||
public PubSubServerManagedTCPConnection(string server_url)
|
public PubSubServerManagedTCPConnection(string serverURL)
|
||||||
: this(new BlutoSubAddress(server_url)) { }
|
: this(new BlutoSubAddress(serverURL)) { }
|
||||||
|
|
||||||
public PubSubServerManagedTCPConnection(BlutoSubAddress server_address) {
|
public PubSubServerManagedTCPConnection(BlutoSubAddress server_address) {
|
||||||
_server_address = server_address;
|
_server_address = server_address;
|
||||||
@@ -50,17 +48,17 @@ namespace BCG {
|
|||||||
|
|
||||||
private void _connect_to_ps_server() {
|
private void _connect_to_ps_server() {
|
||||||
bts.Log.Gen.info($"PubSubServerManagedTCPConnection._connect_to_ps_server server: {_server_address}");
|
bts.Log.Gen.info($"PubSubServerManagedTCPConnection._connect_to_ps_server server: {_server_address}");
|
||||||
AsyncTCPConnector_APM.connect_to(_server_address, this);
|
ClientToServerManagedTCPConnector.connect_to(_server_address, this, TimeSpan.FromSeconds(1));
|
||||||
}
|
}
|
||||||
|
|
||||||
#region IPubSubServerConnection Members
|
#region IPubSubServerConnection Members
|
||||||
|
|
||||||
public void send_with_confirmation(byte[] data, ref string conf_id) {
|
public void sendWithConfirmation(byte[] data, ref string conf_id) {
|
||||||
bts.Log.Gen.info($"PubSubServerManagedTCPConnection.sendWithConfirmation server: {_server_address} data: {data.Length} _connection : {_connection != null}");
|
bts.Log.Gen.info($"PubSubServerManagedTCPConnection.sendWithConfirmation server: {_server_address} data: {data.Length} myConnection : {_connection != null}");
|
||||||
if (_connection != null) {
|
if (_connection != null) {
|
||||||
_connection.sendWithConfirmation(data, ref conf_id, (msg_id, confirmed) => {
|
_connection.sendWithConfirmation(data, ref conf_id, (msg_id, confirmed) => {
|
||||||
if (_on_send_confirm_callback != null)
|
if (_on_send_onfirm_callback != null)
|
||||||
_on_send_confirm_callback(msg_id, confirmed);
|
_on_send_onfirm_callback(msg_id, confirmed);
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
bts.Log.Gen.warn($"PubSubServerManagedTCPConnection.sendWithConfirmation server: {_server_address} no connection, buffering send");
|
bts.Log.Gen.warn($"PubSubServerManagedTCPConnection.sendWithConfirmation server: {_server_address} no connection, buffering send");
|
||||||
@@ -88,18 +86,18 @@ namespace BCG {
|
|||||||
|
|
||||||
public void send_with_confirmation(PubSubMessage msg, ref string conf_id) {
|
public void send_with_confirmation(PubSubMessage msg, ref string conf_id) {
|
||||||
var bytes = msg.to_bytes();
|
var bytes = msg.to_bytes();
|
||||||
send_with_confirmation(bytes, ref conf_id);
|
sendWithConfirmation(bytes, ref conf_id);
|
||||||
}
|
}
|
||||||
|
|
||||||
public IList<byte[]> get_updates(TimeSpan timeout, int maxElements) {
|
public IList<byte[]> getUpdates(TimeSpan timeout, int maxElements) {
|
||||||
return _in_data_queue.dequeueMultipleElements(Convert.ToInt32(timeout.TotalMilliseconds), maxElements);
|
return _in_data_queue.dequeueMultipleElements(Convert.ToInt32(timeout.TotalMilliseconds), maxElements);
|
||||||
}
|
}
|
||||||
|
|
||||||
public string server_url {
|
public string serverURL {
|
||||||
get { return _server_address.ToString(); }
|
get { return _server_address.ToString(); }
|
||||||
}
|
}
|
||||||
|
|
||||||
public string client_url {
|
public string clientURL {
|
||||||
get {
|
get {
|
||||||
string rv = "";
|
string rv = "";
|
||||||
lock (this) {
|
lock (this) {
|
||||||
@@ -120,16 +118,16 @@ namespace BCG {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void register_connection_callback(PubSubServerConnectionStatusChange del) {
|
public void registerCallback(ConnectionStatusChange del) {
|
||||||
_conn_callback = del;
|
_conn_callback = del;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void register_data_callback(PubSubServerDataDelegate del) {
|
public void registerDataCallback(DataDelegate del) {
|
||||||
_data_callback = del;
|
_data_callback = del;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void register_send_confirmation_callback(SendConfirmationDelegate del) {
|
public void registerSendConfirmationCallback(SendConfirmationDelegate del) {
|
||||||
_on_send_confirm_callback = del;
|
_on_send_onfirm_callback = del;
|
||||||
}
|
}
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
@@ -187,6 +185,18 @@ namespace BCG {
|
|||||||
break;
|
break;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
// this stupid shit, caused duplicating og messages and 2 days of wild goose chase
|
||||||
|
// if (message.messageType == RemoteMessageType.Data)
|
||||||
|
// {
|
||||||
|
// if (_data_callback != null)
|
||||||
|
// _data_callback(message.remoteConnectionInfo.ToString(), message.message);
|
||||||
|
// else
|
||||||
|
// _in_data_queue.enqueue(message.message);
|
||||||
|
// }
|
||||||
|
// else
|
||||||
|
// {
|
||||||
|
// _last_hb = DateTime.Now;
|
||||||
|
// }
|
||||||
}
|
}
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
@@ -194,7 +204,8 @@ namespace BCG {
|
|||||||
#region ITCPConnectionHandler Members
|
#region ITCPConnectionHandler Members
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// will be called on ana rbitrary .Net thread
|
/// This function is called on the main thread (as all other functions
|
||||||
|
/// except onData)
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="connection"></param>
|
/// <param name="connection"></param>
|
||||||
public void onConnection(ITCPConnection connection) {
|
public void onConnection(ITCPConnection connection) {
|
||||||
@@ -235,10 +246,10 @@ namespace BCG {
|
|||||||
_connection.register(this);
|
_connection.register(this);
|
||||||
if (_conn_callback != null)
|
if (_conn_callback != null)
|
||||||
_conn_callback(_server_address.ToString(), true);
|
_conn_callback(_server_address.ToString(), true);
|
||||||
if (_on_send_confirm_callback != null) {
|
if (_on_send_onfirm_callback != null) {
|
||||||
var msg_ids = _negative_unsent_confs.dequeueAll();
|
var msg_ids = _negative_unsent_confs.dequeueAll();
|
||||||
foreach (var id in msg_ids)
|
foreach (var id in msg_ids)
|
||||||
_on_send_confirm_callback(id, false);
|
_on_send_onfirm_callback(id, false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -253,6 +264,47 @@ namespace BCG {
|
|||||||
public void doWork() {
|
public void doWork() {
|
||||||
//_dispose_and_reconnect_if_missing_hb();
|
//_dispose_and_reconnect_if_missing_hb();
|
||||||
_check_for_new_connection();
|
_check_for_new_connection();
|
||||||
|
///check for heartbeats and let clients know in
|
||||||
|
///case of missing hb (connection breaking)
|
||||||
|
// TimeSpan hb_interval = DateTime.Now - myLastHB;
|
||||||
|
// bool hb_missed = hb_interval > myConnectionDropThreshold;
|
||||||
|
// bool conn_disposed = myConnection != null || myConnection.isDisposed;
|
||||||
|
// if (conn_disposed || hb_missed)
|
||||||
|
// {
|
||||||
|
// if (hb_missed)
|
||||||
|
// {
|
||||||
|
// // since conn breakage was diagnosed logically, lets physically close the connection
|
||||||
|
// myConnection.Dispose();
|
||||||
|
// string dispose_msg = $"PubSubServerManagedTCPConnection.doWork server: {myServerAddress} Disposing connection conn_disposed: {conn_disposed} hb_missed: {hb_missed}";
|
||||||
|
// Console.WriteLine(dispose_msg);
|
||||||
|
// }
|
||||||
|
// myConnection = null; // this is used as a marker for a dead connection
|
||||||
|
// if (myConnCallback != null)
|
||||||
|
// myConnCallback(myServerAddress.ToString(), false);
|
||||||
|
// string reconn_msg = $"PubSubServerManagedTCPConnection.doWork disconnecting client from server: {myServerAddress} conn_disposed: {conn_disposed} hb_missed: {hb_missed} " +
|
||||||
|
// $"because of missing hbs last_hb: {myLastHB}, now: {DateTime.Now} curr_hb_interval: {hb_interval.TotalSeconds} threshold: {myConnectionDropThreshold.TotalSeconds} conn_disposed: {conn_disposed}";
|
||||||
|
// ClientToServerManagedTCPConnector.connectTo(myServerAddress, this);
|
||||||
|
// }
|
||||||
|
// if (myTempNewConnection != null)
|
||||||
|
// {
|
||||||
|
// if (myConnection != null)
|
||||||
|
// {
|
||||||
|
// // TODOds
|
||||||
|
// // log error and close it
|
||||||
|
// // BlueEventLog.logAssert(myConnection == null, "Connection has to be nulled out and disposed "
|
||||||
|
// // + " before a new connection is established");
|
||||||
|
// }
|
||||||
|
// Console.WriteLine("PubSubServerManagedTCPConnection hooking new connection");
|
||||||
|
// myConnection = myTempNewConnection;
|
||||||
|
// myTempNewConnection = null;
|
||||||
|
// myConnection.register(this);
|
||||||
|
// myConfs.dequeueAll();
|
||||||
|
// if (myConnCallback != null)
|
||||||
|
// myConnCallback(myServerAddress.ToString(), true);
|
||||||
|
// }
|
||||||
|
// foreach (Pair<string, bool> p in myConfs.dequeueAll())
|
||||||
|
// if (myOnSendConfirmCallback != null)
|
||||||
|
// myOnSendConfirmCallback(p.first, p.second);
|
||||||
}
|
}
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|||||||
@@ -16,7 +16,6 @@ namespace BCG {
|
|||||||
// SubMetaData
|
// SubMetaData
|
||||||
// TransactionMetaData
|
// TransactionMetaData
|
||||||
// UnSubMetaData
|
// UnSubMetaData
|
||||||
//
|
|
||||||
public class MetaData : ICloneable {
|
public class MetaData : ICloneable {
|
||||||
private IDictionary _name_value_map = new Dictionary<string, string>();
|
private IDictionary _name_value_map = new Dictionary<string, string>();
|
||||||
int mySize = -1;
|
int mySize = -1;
|
||||||
|
|||||||
@@ -1,8 +1,10 @@
|
|||||||
using System;
|
using System;
|
||||||
|
|
||||||
namespace BCG {
|
namespace BCG
|
||||||
//[Serializable]
|
{
|
||||||
public class SubMetaData {
|
[Serializable]
|
||||||
|
public class SubMetaData
|
||||||
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// connection URL on which the connection will receive data
|
/// connection URL on which the connection will receive data
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -11,18 +13,20 @@ namespace BCG {
|
|||||||
/// identification of the client and the connection on which to receive data
|
/// identification of the client and the connection on which to receive data
|
||||||
/// machine; ip; port; clientURL
|
/// machine; ip; port; clientURL
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public readonly string subscribing_app = ClientInfo.thisClient.processString;
|
public string subscribingApp;
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// tags that are to be subscribed
|
/// tags that are to be subscribed
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public string[] tags;
|
public string[] tags;
|
||||||
|
|
||||||
public SubMetaData() { }
|
public SubMetaData()
|
||||||
|
{ }
|
||||||
|
|
||||||
public SubMetaData(string the_connection, params string[] the_tags) {
|
public SubMetaData(string theConnection, params string[] theTags)
|
||||||
connection = the_connection;
|
{
|
||||||
tags = the_tags;
|
connection = theConnection;
|
||||||
//subscribing_app = ClientInfo.thisClient.processString;
|
tags = theTags;
|
||||||
|
subscribingApp = ClientInfo.thisClient.processString;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,17 +1,21 @@
|
|||||||
using System;
|
using System;
|
||||||
|
|
||||||
namespace BCG {
|
namespace BCG
|
||||||
//[Serializable]
|
{
|
||||||
public class UnSubMetaData {
|
[Serializable]
|
||||||
|
public class UnSubMetaData
|
||||||
|
{
|
||||||
public string connection;
|
public string connection;
|
||||||
public readonly string[] tags;
|
public string[] tags;
|
||||||
public readonly string unsubscribing_app = ClientInfo.thisClient.processString;
|
public string unsubscribingApp = ClientInfo.thisClient.processString;
|
||||||
|
|
||||||
public UnSubMetaData() { }
|
public UnSubMetaData()
|
||||||
|
{}
|
||||||
|
|
||||||
public UnSubMetaData(string the_connection, params string[] the_tags) {
|
public UnSubMetaData(string theConnection, params string[] theTags)
|
||||||
connection = the_connection;
|
{
|
||||||
tags = the_tags;
|
connection = theConnection;
|
||||||
|
tags = theTags;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -6,18 +6,12 @@ namespace BCG
|
|||||||
public interface IPublisher
|
public interface IPublisher
|
||||||
{
|
{
|
||||||
void publish( IPubItem pi );
|
void publish( IPubItem pi );
|
||||||
#pragma warning disable SYSLIB0011 // Type or member is obsolete
|
|
||||||
void publish( IPubItem pi, IFormatter f );
|
void publish( IPubItem pi, IFormatter f );
|
||||||
#pragma warning restore SYSLIB0011 // Type or member is obsolete
|
|
||||||
void publish( IPubItem[] pi );
|
void publish( IPubItem[] pi );
|
||||||
#pragma warning disable SYSLIB0011 // Type or member is obsolete
|
|
||||||
void publish( IPubItem[] pi, IFormatter f );
|
void publish( IPubItem[] pi, IFormatter f );
|
||||||
#pragma warning restore SYSLIB0011 // Type or member is obsolete
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#pragma warning disable SYSLIB0011 // Type or member is obsolete
|
|
||||||
public interface IPublisher<T> where T : IFormatter
|
public interface IPublisher<T> where T : IFormatter
|
||||||
#pragma warning restore SYSLIB0011 // Type or member is obsolete
|
|
||||||
{
|
{
|
||||||
void publish (IPubItem pi);
|
void publish (IPubItem pi);
|
||||||
void publish (IPubItem[] pi);
|
void publish (IPubItem[] pi);
|
||||||
|
|||||||
@@ -8,9 +8,7 @@ namespace BCG
|
|||||||
public class Publisher : IPublisher
|
public class Publisher : IPublisher
|
||||||
{
|
{
|
||||||
private PublisherCore myPubCore = new PublisherCore ();
|
private PublisherCore myPubCore = new PublisherCore ();
|
||||||
#pragma warning disable SYSLIB0011 // Type or member is obsolete
|
|
||||||
private BinaryFormatter myBinFormatter = new BinaryFormatter ();
|
private BinaryFormatter myBinFormatter = new BinaryFormatter ();
|
||||||
#pragma warning restore SYSLIB0011 // Type or member is obsolete
|
|
||||||
private const string myTagType = "DefaultTag";
|
private const string myTagType = "DefaultTag";
|
||||||
|
|
||||||
public Publisher() {}
|
public Publisher() {}
|
||||||
@@ -23,9 +21,7 @@ namespace BCG
|
|||||||
public void publish (IPubItem[] pis)
|
public void publish (IPubItem[] pis)
|
||||||
{ publish( pis, myBinFormatter ); }
|
{ publish( pis, myBinFormatter ); }
|
||||||
|
|
||||||
#pragma warning disable SYSLIB0011 // Type or member is obsolete
|
|
||||||
public void publish( IPubItem pi, IFormatter f )
|
public void publish( IPubItem pi, IFormatter f )
|
||||||
#pragma warning restore SYSLIB0011 // Type or member is obsolete
|
|
||||||
{
|
{
|
||||||
MemoryStream ms = new MemoryStream();
|
MemoryStream ms = new MemoryStream();
|
||||||
#pragma warning disable
|
#pragma warning disable
|
||||||
@@ -35,9 +31,7 @@ namespace BCG
|
|||||||
myPubCore.publish( myTagType, pi.getTag(), ms );
|
myPubCore.publish( myTagType, pi.getTag(), ms );
|
||||||
}
|
}
|
||||||
|
|
||||||
#pragma warning disable SYSLIB0011 // Type or member is obsolete
|
|
||||||
public void publish( IPubItem[] pis, IFormatter f )
|
public void publish( IPubItem[] pis, IFormatter f )
|
||||||
#pragma warning restore SYSLIB0011 // Type or member is obsolete
|
|
||||||
{
|
{
|
||||||
myPubCore.beginTransaction();
|
myPubCore.beginTransaction();
|
||||||
foreach ( IPubItem pi in pis )
|
foreach ( IPubItem pi in pis )
|
||||||
@@ -48,14 +42,10 @@ namespace BCG
|
|||||||
#endregion
|
#endregion
|
||||||
}
|
}
|
||||||
|
|
||||||
#pragma warning disable SYSLIB0011 // Type or member is obsolete
|
|
||||||
public class Publisher<T> : IPublisher<T> where T : IFormatter, new ()
|
public class Publisher<T> : IPublisher<T> where T : IFormatter, new ()
|
||||||
#pragma warning restore SYSLIB0012 // Type or member is obsolete
|
|
||||||
{
|
{
|
||||||
private PublisherCore myPubCore = new PublisherCore ();
|
private PublisherCore myPubCore = new PublisherCore ();
|
||||||
#pragma warning disable SYSLIB0011 // Type or member is obsolete
|
|
||||||
private IFormatter myFormatter = new T ();
|
private IFormatter myFormatter = new T ();
|
||||||
#pragma warning restore SYSLIB0011 // Type or member is obsolete
|
|
||||||
private const string myTagType = "DefaultTag";
|
private const string myTagType = "DefaultTag";
|
||||||
|
|
||||||
public Publisher () { }
|
public Publisher () { }
|
||||||
@@ -65,9 +55,9 @@ namespace BCG
|
|||||||
public void publish (IPubItem pi)
|
public void publish (IPubItem pi)
|
||||||
{
|
{
|
||||||
MemoryStream ms = new MemoryStream ();
|
MemoryStream ms = new MemoryStream ();
|
||||||
#pragma warning disable
|
#pragma warning disable
|
||||||
myFormatter.Serialize (ms, pi);
|
myFormatter.Serialize (ms, pi);
|
||||||
#pragma warning restore SYSLIB0011
|
#pragma warning restore SYSLIB0011
|
||||||
ms.Position = 0;
|
ms.Position = 0;
|
||||||
myPubCore.publish (myTagType, pi.getTag (), ms);
|
myPubCore.publish (myTagType, pi.getTag (), ms);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -328,8 +328,7 @@ namespace bts {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
_send_key_map.Add(message.key);
|
_send_key_map.Add(message.key);
|
||||||
bts.Log.Gen.info($"AsyncTCPConnection_APM._on_send: pre endSend this: {ToString()} tid: {_tid()} " +
|
bts.Log.Gen.info($"AsyncTCPConnection_APM._on_send: pre endSend this: {ToString()} tid: {_tid()} res: {_cb_txt(res)} message: {res.AsyncState} _is_alive: {_is_alive}");
|
||||||
$"res: {_cb_txt(res)} message: {res.AsyncState} _is_alive: {_is_alive}");
|
|
||||||
if (_is_alive) {
|
if (_is_alive) {
|
||||||
try {
|
try {
|
||||||
SocketError send_err;
|
SocketError send_err;
|
||||||
|
|||||||
@@ -1,82 +1,82 @@
|
|||||||
// using System;
|
using System;
|
||||||
// using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
// using System.Text;
|
using System.Text;
|
||||||
// using System.Net;
|
using System.Net;
|
||||||
// using System.Net.Sockets;
|
using System.Net.Sockets;
|
||||||
// using System.Threading;
|
using System.Threading;
|
||||||
// using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
// //using BCG.Utilities;
|
//using BCG.Utilities;
|
||||||
// //using BCG.Utilities.General;
|
//using BCG.Utilities.General;
|
||||||
|
|
||||||
// namespace BCG {
|
namespace BCG {
|
||||||
// internal class SocketConnectionArgs {
|
internal class SocketConnectionArgs {
|
||||||
// public EndPoint ep;
|
public EndPoint ep;
|
||||||
// public Socket sock;
|
public Socket sock;
|
||||||
// public ITCPConnectionHandler handler;
|
public ITCPConnectionHandler handler;
|
||||||
// public string serverURL;
|
public string serverURL;
|
||||||
|
|
||||||
// internal SocketConnectionArgs(EndPoint theEp, Socket theSock, ITCPConnectionHandler theHandler,
|
internal SocketConnectionArgs(EndPoint theEp, Socket theSock, ITCPConnectionHandler theHandler,
|
||||||
// string theServerURL) {
|
string theServerURL) {
|
||||||
// ep = theEp;
|
ep = theEp;
|
||||||
// sock = theSock;
|
sock = theSock;
|
||||||
// handler = theHandler;
|
handler = theHandler;
|
||||||
// serverURL = theServerURL;
|
serverURL = theServerURL;
|
||||||
// }
|
}
|
||||||
// }
|
}
|
||||||
|
|
||||||
// public class ClientToServerManagedTCPConnector {
|
public class ClientToServerManagedTCPConnector {
|
||||||
// private static Random myRnd = new Random(DateTime.Now.Millisecond);
|
private static Random myRnd = new Random(DateTime.Now.Millisecond);
|
||||||
// private static TimeSpan myHBTimeSpan = new TimeSpan(0, 0, 0, 0, PubSub3Config.instance.PS3_HeartBeatInterval);
|
private static TimeSpan myHBTimeSpan = new TimeSpan(0, 0, 0, 0, PubSub3Config.instance.PS3_HeartBeatInterval);
|
||||||
// private static int myWaitTimeBeforeRetryingUpperLimit = 5000,
|
private static int myWaitTimeBeforeRetryingUpperLimit = 5000,
|
||||||
// myWaitTimeBeforeRetryingLowerLimit = 1000;
|
myWaitTimeBeforeRetryingLowerLimit = 1000;
|
||||||
|
|
||||||
// public static void connectTo(BlutoSubAddress serverAddress, ITCPConnectionHandler handler) {
|
public static void connectTo(BlutoSubAddress serverAddress, ITCPConnectionHandler handler) {
|
||||||
// Console.WriteLine($"ClientToServerManagedTCPConnector.connectTo server: {serverAddress}");
|
Console.WriteLine($"ClientToServerManagedTCPConnector.connectTo server: {serverAddress}");
|
||||||
// Socket sock = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
|
Socket sock = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
|
||||||
// EndPoint ep = new IPEndPoint(IPAddress.Parse(serverAddress.ip), serverAddress.port);
|
EndPoint ep = new IPEndPoint(IPAddress.Parse(serverAddress.ip), serverAddress.port);
|
||||||
// sock.BeginConnect(ep, new AsyncCallback(myOnConnect), new SocketConnectionArgs(ep, sock, handler, serverAddress.ToString()));
|
sock.BeginConnect(ep, new AsyncCallback(myOnConnect), new SocketConnectionArgs(ep, sock, handler, serverAddress.ToString()));
|
||||||
// }
|
}
|
||||||
|
|
||||||
// public static async Task connect_to_async(BlutoSubAddress serverAddress, ITCPConnectionHandler handler, TimeSpan wait_before_retrying, int max_n_attempts = 1024) {
|
public static async Task connect_to_async(BlutoSubAddress serverAddress, ITCPConnectionHandler handler, TimeSpan wait_before_retrying, int max_n_attempts = 1024) {
|
||||||
// bts.Log.Gen.info($"ClientToServerManagedTCPConnector.connectTo server: {serverAddress}");
|
bts.Log.Gen.info($"ClientToServerManagedTCPConnector.connectTo server: {serverAddress}");
|
||||||
// await TCPSocketUtils.connect_to_server_async(serverAddress.ip, serverAddress.port, handler, wait_before_retrying, max_n_attempts);
|
await TCPSocketUtils.connect_to_server_async(serverAddress.ip, serverAddress.port, handler, wait_before_retrying, max_n_attempts);
|
||||||
// }
|
}
|
||||||
|
|
||||||
// public static void connect_to(BlutoSubAddress serverAddress, ITCPConnectionHandler handler, TimeSpan wait_before_retrying, int max_n_attempts = 1024) {
|
public static void connect_to(BlutoSubAddress serverAddress, ITCPConnectionHandler handler, TimeSpan wait_before_retrying, int max_n_attempts = 1024) {
|
||||||
// bts.Log.Gen.info($"ClientToServerManagedTCPConnector.connectTo server: {serverAddress}");
|
bts.Log.Gen.info($"ClientToServerManagedTCPConnector.connectTo server: {serverAddress}");
|
||||||
// Task.Run(async () => {
|
Task.Run(async () => {
|
||||||
// await TCPSocketUtils.connect_to_server_async(serverAddress.ip, serverAddress.port, handler, wait_before_retrying, max_n_attempts);
|
await TCPSocketUtils.connect_to_server_async(serverAddress.ip, serverAddress.port, handler, wait_before_retrying, max_n_attempts);
|
||||||
// });
|
});
|
||||||
// }
|
}
|
||||||
|
|
||||||
// private static void myOnConnect(IAsyncResult res) {
|
private static void myOnConnect(IAsyncResult res) {
|
||||||
// Console.WriteLine("ClientToServerManagedTCPConnector.myOnConnect " + res.ToString() + " " + res.AsyncState.ToString());
|
Console.WriteLine("ClientToServerManagedTCPConnector.myOnConnect " + res.ToString() + " " + res.AsyncState.ToString());
|
||||||
// SocketConnectionArgs args = res.AsyncState as SocketConnectionArgs;
|
SocketConnectionArgs args = res.AsyncState as SocketConnectionArgs;
|
||||||
// try {
|
try {
|
||||||
// args.sock.EndConnect(res);
|
args.sock.EndConnect(res);
|
||||||
// } catch (SocketException sex) {
|
} catch (SocketException sex) {
|
||||||
// string errMsg = String.Format("{0}: Connection to {1} failed.", DateTime.Now.ToString(), args.serverURL.ToString());
|
string errMsg = String.Format("{0}: Connection to {1} failed.", DateTime.Now.ToString(), args.serverURL.ToString());
|
||||||
// //BlueEventLog.logEvent(sex.Message + ";" + errMsg, PS3LogLevels.disconnection);
|
//BlueEventLog.logEvent(sex.Message + ";" + errMsg, PS3LogLevels.disconnection);
|
||||||
// //Console.WriteLine(errMsg);
|
//Console.WriteLine(errMsg);
|
||||||
// if (args.handler.isDisposed) {
|
if (args.handler.isDisposed) {
|
||||||
// string doneMsg = String.Format("{0}: Connection to {1} was disposed. Not Retrying.", DateTime.Now.ToString(), args.serverURL.ToString());
|
string doneMsg = String.Format("{0}: Connection to {1} was disposed. Not Retrying.", DateTime.Now.ToString(), args.serverURL.ToString());
|
||||||
// //Console.WriteLine(doneMsg);
|
//Console.WriteLine(doneMsg);
|
||||||
// // BlueEventLog.logEvent(doneMsg, PS3LogLevels.disconnection);
|
// BlueEventLog.logEvent(doneMsg, PS3LogLevels.disconnection);
|
||||||
// return;
|
return;
|
||||||
// } else {
|
} else {
|
||||||
// string retryMsg = String.Format("{0}: Retrying Connection to {1}.", DateTime.Now.ToString(), args.serverURL.ToString());
|
string retryMsg = String.Format("{0}: Retrying Connection to {1}.", DateTime.Now.ToString(), args.serverURL.ToString());
|
||||||
// //Console.WriteLine(retryMsg);
|
//Console.WriteLine(retryMsg);
|
||||||
// //BlueEventLog.logEvent(retryMsg, PS3LogLevels.disconnection);
|
//BlueEventLog.logEvent(retryMsg, PS3LogLevels.disconnection);
|
||||||
|
|
||||||
// int waitTime = myRnd.Next(myWaitTimeBeforeRetryingLowerLimit, myWaitTimeBeforeRetryingUpperLimit);
|
int waitTime = myRnd.Next(myWaitTimeBeforeRetryingLowerLimit, myWaitTimeBeforeRetryingUpperLimit);
|
||||||
// Thread.Sleep(waitTime);
|
Thread.Sleep(waitTime);
|
||||||
// args.sock.BeginConnect(args.ep, new AsyncCallback(myOnConnect), args);
|
args.sock.BeginConnect(args.ep, new AsyncCallback(myOnConnect), args);
|
||||||
// return;
|
return;
|
||||||
// }
|
}
|
||||||
// }
|
}
|
||||||
// //BlueEventLog.logEvent(String.Format("Successful connection made to {0}.", args.serverURL.ToString()), PS3LogLevels.connection);
|
//BlueEventLog.logEvent(String.Format("Successful connection made to {0}.", args.serverURL.ToString()), PS3LogLevels.connection);
|
||||||
// args.handler.onConnection(new bts.AsyncTCPConnection_APM(args.sock, myHBTimeSpan, ITCPConnection.ConnectionSide.Client));
|
args.handler.onConnection(new bts.AsyncTCPConnection_APM(args.sock, myHBTimeSpan, ITCPConnection.ConnectionSide.Client));
|
||||||
// }
|
}
|
||||||
|
|
||||||
// }
|
}
|
||||||
// }
|
}
|
||||||
@@ -5,21 +5,23 @@ using System.Runtime.Serialization.Formatters.Binary;
|
|||||||
|
|
||||||
namespace BCG
|
namespace BCG
|
||||||
{
|
{
|
||||||
#pragma warning disable SYSLIB0011 // Type or member is obsolete
|
|
||||||
public class GenericFormatter<F> : IGenericFormatter where F : IFormatter, new ()
|
public class GenericFormatter<F> : IGenericFormatter where F : IFormatter, new ()
|
||||||
{
|
{
|
||||||
IFormatter myFormatter = new F ();
|
IFormatter myFormatter = new F ();
|
||||||
|
|
||||||
public T Deserialize<T> (Stream serializationStream)
|
public T Deserialize<T> (Stream serializationStream)
|
||||||
{
|
{
|
||||||
|
#pragma warning disable
|
||||||
return (T) myFormatter.Deserialize (serializationStream);
|
return (T) myFormatter.Deserialize (serializationStream);
|
||||||
|
#pragma warning restore SYSLIB0011
|
||||||
}
|
}
|
||||||
public void Serialize<T> (Stream serializationStream, T graph)
|
public void Serialize<T> (Stream serializationStream, T graph)
|
||||||
{
|
{
|
||||||
|
#pragma warning disable
|
||||||
myFormatter.Serialize (serializationStream, graph);
|
myFormatter.Serialize (serializationStream, graph);
|
||||||
|
#pragma warning restore SYSLIB0011
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public class GenericBinaryFormatter : GenericFormatter<BinaryFormatter> { }
|
public class GenericBinaryFormatter : GenericFormatter<BinaryFormatter> { }
|
||||||
#pragma warning restore SYSLIB0011 // Type or member is obsolete
|
|
||||||
}
|
}
|
||||||
@@ -6,7 +6,7 @@
|
|||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
||||||
<TargetFramework>net8.0</TargetFramework>
|
<TargetFramework>net7.0</TargetFramework>
|
||||||
<Nullable>enable</Nullable>
|
<Nullable>enable</Nullable>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
|
|||||||
@@ -4,22 +4,19 @@ using System.IO;
|
|||||||
|
|
||||||
namespace BCG {
|
namespace BCG {
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// this is the data returned from the pubsub server
|
/// this is the data returned from the
|
||||||
|
/// pubsub server
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class PubSubUpdate {
|
public class PubSubUpdate {
|
||||||
|
|
||||||
public PubSubUpdate(string tag, string the_tag_type, Stream body) {
|
public PubSubUpdate(string tag, string tagType, Stream body) { this.tag = tag; this.body = body; this.tagType = tagType; }
|
||||||
this.tag = tag;
|
|
||||||
this.body = body;
|
|
||||||
this.tag_type = the_tag_type;
|
|
||||||
}
|
|
||||||
|
|
||||||
public string tag;
|
public string tag;
|
||||||
public Stream body;
|
public Stream body;
|
||||||
public string tag_type;
|
public string tagType;
|
||||||
|
|
||||||
public override string ToString() {
|
public override string ToString() {
|
||||||
return $"PubSubUpdate tag: {tag} tag_type: {tag_type} len: {body.Position}";
|
return $"PubSubUpdate tag: {tag} tag_type: {tagType} len: {body.Position}";
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -5,16 +5,18 @@ using System.IO;
|
|||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using mbts;
|
using mbts;
|
||||||
|
|
||||||
namespace BCG {
|
namespace BCG
|
||||||
public delegate void PubSubServerConnectionStatusChange(string peerURL, bool connection);
|
{
|
||||||
public delegate void PubSubServerDataDelegate(string sender, byte[] data);
|
public delegate void ConnectionStatusChange(string peerURL, bool connection);
|
||||||
|
public delegate void DataDelegate(string sender, byte[] data);
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Encapsulates PubsubServer's communication with the
|
/// Encapsulates PubsubServer's communication with the
|
||||||
/// rest of the world. Has send and get methods, and also
|
/// rest of the world. Has send and get methods, and also
|
||||||
/// an event to tell if a client connects or disconnects.
|
/// an event to tell if a client connects or disconnects.
|
||||||
/// this event is prompted by the doWork.
|
/// this event is prompted by the doWork.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public interface IPubsubMessageHub : IWorker {
|
public interface IPubsubMessageHub : IWorker
|
||||||
|
{
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The port that the message hub is listening on. Return -1 if you're not listening on a port, i.e. for hte case of multicast
|
/// The port that the message hub is listening on. Return -1 if you're not listening on a port, i.e. for hte case of multicast
|
||||||
@@ -27,14 +29,14 @@ namespace BCG {
|
|||||||
/// getUpdates. The delegate will be fired on an arbitrary thread.
|
/// getUpdates. The delegate will be fired on an arbitrary thread.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="?"></param>
|
/// <param name="?"></param>
|
||||||
void registerDataCallback(PubSubServerDataDelegate del);
|
void registerDataCallback(DataDelegate del);
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// registers a connection callback. this callback
|
/// registers a connection callback. this callback
|
||||||
/// will be fired within the doWork call.
|
/// will be fired within the doWork call.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="del"></param>
|
/// <param name="del"></param>
|
||||||
/// <param name="register"></param>
|
/// <param name="register"></param>
|
||||||
void registerConnectionCallback(PubSubServerConnectionStatusChange del);
|
void registerConnectionCallback(ConnectionStatusChange del);
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// this is where this interface starts communication with the
|
/// this is where this interface starts communication with the
|
||||||
/// rest of the world. Obvioulsy, the resgiters should be
|
/// rest of the world. Obvioulsy, the resgiters should be
|
||||||
|
|||||||
@@ -22,8 +22,8 @@ namespace BCG {
|
|||||||
|
|
||||||
}
|
}
|
||||||
private bool myActivated = false;
|
private bool myActivated = false;
|
||||||
private PubSubServerConnectionStatusChange myConnCb;
|
private ConnectionStatusChange myConnCb;
|
||||||
private PubSubServerDataDelegate myDataCb;
|
private DataDelegate myDataCb;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// client to connection
|
/// client to connection
|
||||||
@@ -77,12 +77,12 @@ namespace BCG {
|
|||||||
|
|
||||||
#region IPubsubMessageHub Members
|
#region IPubsubMessageHub Members
|
||||||
|
|
||||||
public void registerDataCallback(PubSubServerDataDelegate del) {
|
public void registerDataCallback(DataDelegate del) {
|
||||||
// BlueEventLog.logAssert(!myActivated, "connection callbacks should be registered before the call to activate");
|
// BlueEventLog.logAssert(!myActivated, "connection callbacks should be registered before the call to activate");
|
||||||
myDataCb = del;
|
myDataCb = del;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void registerConnectionCallback(PubSubServerConnectionStatusChange del) {
|
public void registerConnectionCallback(ConnectionStatusChange del) {
|
||||||
//BlueEventLog.logAssert(!myActivated, "data callbacks should be registered before the call to activate");
|
//BlueEventLog.logAssert(!myActivated, "data callbacks should be registered before the call to activate");
|
||||||
myConnCb = del;
|
myConnCb = del;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,22 +4,25 @@ using System.Threading;
|
|||||||
using System.IO;
|
using System.IO;
|
||||||
using mbts;
|
using mbts;
|
||||||
|
|
||||||
namespace BCG {
|
namespace BCG
|
||||||
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Wraps around all the IPubsubMessageHub's that pubsub supports.
|
/// Wraps around all the IPubsubMessageHub's that pubsub supports.
|
||||||
/// ie MSMQ and TCP to begin with.
|
/// ie MSMQ and TCP to begin with.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class ServerMessageHub : IPubsubMessageHub {
|
public class ServerMessageHub : IPubsubMessageHub
|
||||||
|
{
|
||||||
private static ServerMessageHub mySelf = null;
|
private static ServerMessageHub mySelf = null;
|
||||||
private static object myInstanceLock = new object();
|
private static object myInstanceLock = new object();
|
||||||
private Dictionary<string, IPubsubMessageHub> myHubs = new Dictionary<string, IPubsubMessageHub>();
|
private Dictionary<string, IPubsubMessageHub> myHubs = new Dictionary<string, IPubsubMessageHub>();
|
||||||
private SyncQueue<Pair<string, byte[]>> myReceivingQueue = new SyncQueue<Pair<string, byte[]>>();
|
private SyncQueue<Pair<string, byte[]>> myReceivingQueue = new SyncQueue<Pair<string, byte[]>>();
|
||||||
private PubSubServerConnectionStatusChange myConnectionCb;
|
private ConnectionStatusChange myConnectionCb;
|
||||||
private PubSubServerDataDelegate myDataCb;
|
private DataDelegate myDataCb;
|
||||||
private MasterWorker myMasterWorker = MasterWorker.getInstance(false);
|
private MasterWorker myMasterWorker = MasterWorker.getInstance(false);
|
||||||
|
|
||||||
protected ServerMessageHub() : this(PubSub3Config.instance.PS3_ServerPort) { }
|
protected ServerMessageHub() : this(PubSub3Config.instance.PS3_ServerPort) { }
|
||||||
protected ServerMessageHub(int portNumber) {
|
protected ServerMessageHub(int portNumber)
|
||||||
|
{
|
||||||
// TODO: no more MSMQ
|
// TODO: no more MSMQ
|
||||||
// if (!PubSub3Config.instance.PS3_DisableMSMQ)
|
// if (!PubSub3Config.instance.PS3_DisableMSMQ)
|
||||||
// myHubs.Add(MSMQSubAddress.prefix, new MSMQMessageHub());
|
// myHubs.Add(MSMQSubAddress.prefix, new MSMQMessageHub());
|
||||||
@@ -32,14 +35,19 @@ namespace BCG {
|
|||||||
as IPubsubMessageHub);
|
as IPubsubMessageHub);
|
||||||
}
|
}
|
||||||
|
|
||||||
public int port {
|
public int port
|
||||||
|
{
|
||||||
get { return myHubs[BlutoSubAddress.prefix].port; }
|
get { return myHubs[BlutoSubAddress.prefix].port; }
|
||||||
|
|
||||||
}
|
}
|
||||||
public static ServerMessageHub instance {
|
public static ServerMessageHub instance
|
||||||
get {
|
{
|
||||||
if (mySelf == null) {
|
get
|
||||||
lock (myInstanceLock) {
|
{
|
||||||
|
if (mySelf == null)
|
||||||
|
{
|
||||||
|
lock (myInstanceLock)
|
||||||
|
{
|
||||||
if (mySelf == null)
|
if (mySelf == null)
|
||||||
mySelf = new ServerMessageHub();
|
mySelf = new ServerMessageHub();
|
||||||
}
|
}
|
||||||
@@ -52,39 +60,47 @@ namespace BCG {
|
|||||||
|
|
||||||
/* Builds the server message hub with a specified port number. Only works if the server message hub has not already been created.
|
/* Builds the server message hub with a specified port number. Only works if the server message hub has not already been created.
|
||||||
* */
|
* */
|
||||||
public static ServerMessageHub getInstance(int portNumber) {
|
public static ServerMessageHub getInstance(int portNumber)
|
||||||
if (mySelf == null) {
|
{
|
||||||
lock (myInstanceLock) {
|
if (mySelf == null)
|
||||||
|
{
|
||||||
|
lock (myInstanceLock)
|
||||||
|
{
|
||||||
if (mySelf == null)
|
if (mySelf == null)
|
||||||
mySelf = new ServerMessageHub(portNumber);
|
mySelf = new ServerMessageHub(portNumber);
|
||||||
}
|
}
|
||||||
|
|
||||||
} else
|
}
|
||||||
|
else
|
||||||
throw new ArgumentException("Error: Cannot specify a port number for the ServerMessageHub instance if it has already been created.");
|
throw new ArgumentException("Error: Cannot specify a port number for the ServerMessageHub instance if it has already been created.");
|
||||||
return mySelf;
|
return mySelf;
|
||||||
}
|
}
|
||||||
#region IPubsubMessageHub Members
|
#region IPubsubMessageHub Members
|
||||||
|
|
||||||
public void registerDataCallback(PubSubServerDataDelegate del) { myDataCb = del; }
|
public void registerDataCallback(DataDelegate del) { myDataCb = del; }
|
||||||
|
|
||||||
public void registerConnectionCallback(PubSubServerConnectionStatusChange del) { myConnectionCb = del; }
|
public void registerConnectionCallback(ConnectionStatusChange del) { myConnectionCb = del; }
|
||||||
|
|
||||||
public void activate() {
|
public void activate()
|
||||||
|
{
|
||||||
string msg = "PubSubServer started .. initializing TCP transport components .. ";
|
string msg = "PubSubServer started .. initializing TCP transport components .. ";
|
||||||
Console.WriteLine(msg);
|
Console.WriteLine(msg);
|
||||||
foreach (IPubsubMessageHub hub in myHubs.Values) {
|
foreach (IPubsubMessageHub hub in myHubs.Values)
|
||||||
hub.registerDataCallback(new PubSubServerDataDelegate(myOnData));
|
{
|
||||||
hub.registerConnectionCallback(new PubSubServerConnectionStatusChange(myOnConnection));
|
hub.registerDataCallback(new DataDelegate(myOnData));
|
||||||
|
hub.registerConnectionCallback(new ConnectionStatusChange(myOnConnection));
|
||||||
hub.activate();
|
hub.activate();
|
||||||
}
|
}
|
||||||
myMasterWorker.addWorker(this, new TimeSpan(0, 0, 0, 0, 250));
|
myMasterWorker.addWorker(this, new TimeSpan(0, 0, 0, 0, 250));
|
||||||
}
|
}
|
||||||
|
|
||||||
public List<Pair<string, byte[]>> getUpdates(TimeSpan timeout, int maxElements) {
|
public List<Pair<string, byte[]>> getUpdates(TimeSpan timeout, int maxElements)
|
||||||
|
{
|
||||||
return myReceivingQueue.dequeueMultipleElements(timeout, maxElements);
|
return myReceivingQueue.dequeueMultipleElements(timeout, maxElements);
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool send(string receiver, byte[] data) {
|
public bool send(string receiver, byte[] data)
|
||||||
|
{
|
||||||
if (receiver.StartsWith(BlutoSubAddress.prefix))
|
if (receiver.StartsWith(BlutoSubAddress.prefix))
|
||||||
return myHubs[BlutoSubAddress.prefix].send(receiver, data);
|
return myHubs[BlutoSubAddress.prefix].send(receiver, data);
|
||||||
else if (receiver.StartsWith(MSMQSubAddress.prefix))
|
else if (receiver.StartsWith(MSMQSubAddress.prefix))
|
||||||
@@ -93,7 +109,8 @@ namespace BCG {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool send(string receiver, Stream str) {
|
public bool send(string receiver, Stream str)
|
||||||
|
{
|
||||||
if (receiver.StartsWith(BlutoSubAddress.prefix))
|
if (receiver.StartsWith(BlutoSubAddress.prefix))
|
||||||
return myHubs[BlutoSubAddress.prefix].send(receiver, str);
|
return myHubs[BlutoSubAddress.prefix].send(receiver, str);
|
||||||
else if (receiver.StartsWith(MSMQSubAddress.prefix))
|
else if (receiver.StartsWith(MSMQSubAddress.prefix))
|
||||||
@@ -109,7 +126,8 @@ namespace BCG {
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// connection callback will be called within this doWork
|
/// connection callback will be called within this doWork
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public void doWork() {
|
public void doWork()
|
||||||
|
{
|
||||||
foreach (IPubsubMessageHub hub in myHubs.Values)
|
foreach (IPubsubMessageHub hub in myHubs.Values)
|
||||||
hub.doWork();
|
hub.doWork();
|
||||||
}
|
}
|
||||||
@@ -120,18 +138,21 @@ namespace BCG {
|
|||||||
public static string internalPublisherConnection = "InternalPublisher";
|
public static string internalPublisherConnection = "InternalPublisher";
|
||||||
|
|
||||||
|
|
||||||
public void internalSend(byte[] data) {
|
public void internalSend(byte[] data)
|
||||||
|
{
|
||||||
myReceivingQueue.enqueue(new Pair<string, byte[]>(internalPublisherConnection, data));
|
myReceivingQueue.enqueue(new Pair<string, byte[]>(internalPublisherConnection, data));
|
||||||
}
|
}
|
||||||
|
|
||||||
private void myOnData(string sender, byte[] msg) {
|
private void myOnData(string sender, byte[] msg)
|
||||||
|
{
|
||||||
if (myDataCb != null)
|
if (myDataCb != null)
|
||||||
myOnData(sender, msg);
|
myOnData(sender, msg);
|
||||||
else
|
else
|
||||||
myReceivingQueue.enqueue(new Pair<string, byte[]>(sender, msg));
|
myReceivingQueue.enqueue(new Pair<string, byte[]>(sender, msg));
|
||||||
}
|
}
|
||||||
|
|
||||||
private void myOnConnection(string clientURL, bool connected) {
|
private void myOnConnection(string clientURL, bool connected)
|
||||||
|
{
|
||||||
if (myConnectionCb != null)
|
if (myConnectionCb != null)
|
||||||
myConnectionCb(clientURL, connected);
|
myConnectionCb(clientURL, connected);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -58,7 +58,7 @@ namespace BCG {
|
|||||||
|
|
||||||
myReceivingProcThread = new Thread(new ThreadStart(myReceivingThreadProc));
|
myReceivingProcThread = new Thread(new ThreadStart(myReceivingThreadProc));
|
||||||
myMessageHub = ServerMessageHub.instance;
|
myMessageHub = ServerMessageHub.instance;
|
||||||
myMessageHub.registerConnectionCallback(new PubSubServerConnectionStatusChange(myHandleConnections));
|
myMessageHub.registerConnectionCallback(new ConnectionStatusChange(myHandleConnections));
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -55,7 +55,7 @@ namespace BCG {
|
|||||||
myTagToPubsubMap[tag].onSub(sub);
|
myTagToPubsubMap[tag].onSub(sub);
|
||||||
|
|
||||||
if (onSingleTagChange != null)
|
if (onSingleTagChange != null)
|
||||||
onSingleTagChange(new PubSubSingleTagStateEvent(tag, PubSubSingleTagStateEvent.EventType.subscription, sub.connection, sub.subscribing_app));
|
onSingleTagChange(new PubSubSingleTagStateEvent(tag, PubSubSingleTagStateEvent.EventType.subscription, sub.connection, sub.subscribingApp));
|
||||||
}
|
}
|
||||||
if (onTagMapChanged != null)
|
if (onTagMapChanged != null)
|
||||||
onTagMapChanged(sub.tags);
|
onTagMapChanged(sub.tags);
|
||||||
@@ -68,10 +68,10 @@ namespace BCG {
|
|||||||
///and publishers for the tag then remove it.
|
///and publishers for the tag then remove it.
|
||||||
myTagToPubsubMap[tag].onUnsub(unsub);
|
myTagToPubsubMap[tag].onUnsub(unsub);
|
||||||
if (onSingleTagChange != null)
|
if (onSingleTagChange != null)
|
||||||
onSingleTagChange(new PubSubSingleTagStateEvent(tag, PubSubSingleTagStateEvent.EventType.unsubscription, unsub.connection, unsub.unsubscribing_app));
|
onSingleTagChange(new PubSubSingleTagStateEvent(tag, PubSubSingleTagStateEvent.EventType.unsubscription, unsub.connection, unsub.unsubscribingApp));
|
||||||
} else {
|
} else {
|
||||||
string warningMsg = String.Format("{0} was never published or subscribed on this pubsub, hence" +
|
string warningMsg = String.Format("{0} was never published or subscribed on this pubsub, hence" +
|
||||||
" the unsub call by app {1} is voided", tag, unsub.unsubscribing_app);
|
" the unsub call by app {1} is voided", tag, unsub.unsubscribingApp);
|
||||||
//BlueEventLog.logEvent(warningMsg, BlueEventLevel.Warning);
|
//BlueEventLog.logEvent(warningMsg, BlueEventLevel.Warning);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,19 +1,19 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
|
|
||||||
using BCG.Utilities;
|
using BCG.Utilities;
|
||||||
|
|
||||||
using mbts;
|
using mbts;
|
||||||
|
|
||||||
namespace BCG {
|
namespace BCG
|
||||||
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// SingleTagPubSubMap consists of all the state w.r.t to
|
/// SingleTagPubSubMap consists of all the state w.r.t to
|
||||||
/// publishing and subscription of a single tag. this information
|
/// publishing and subscription of a single tag. this information
|
||||||
/// consists of the publisher, and a map of the subscriber
|
/// consists of the publisher, and a map of the subscriber
|
||||||
/// connection vs the apps they're coming from
|
/// connection vs the apps they're coming from
|
||||||
/// </summary>
|
/// </summary>
|
||||||
internal class SingleTagPubSubMap {
|
internal class SingleTagPubSubMap
|
||||||
|
{
|
||||||
private string myTag;
|
private string myTag;
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// map of clientURL to client app (machine;app;pid;clientURL)
|
/// map of clientURL to client app (machine;app;pid;clientURL)
|
||||||
@@ -29,7 +29,8 @@ namespace BCG {
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
List<Pair<string, string>> myDeadPubConnToApp = new List<Pair<string, string>>();
|
List<Pair<string, string>> myDeadPubConnToApp = new List<Pair<string, string>>();
|
||||||
|
|
||||||
public SingleTagPubSubMap(string tag) {
|
public SingleTagPubSubMap(string tag)
|
||||||
|
{
|
||||||
myTag = tag;
|
myTag = tag;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -37,7 +38,8 @@ namespace BCG {
|
|||||||
/// Returns a list all the active connections that
|
/// Returns a list all the active connections that
|
||||||
/// have subscribed to this tag
|
/// have subscribed to this tag
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public ICollection<string> subscriberConnections {
|
public ICollection<string> subscriberConnections
|
||||||
|
{
|
||||||
get { return mySubConnToApp.Keys; }
|
get { return mySubConnToApp.Keys; }
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -45,7 +47,8 @@ namespace BCG {
|
|||||||
/// Returns a list of all the applications that have
|
/// Returns a list of all the applications that have
|
||||||
/// subscribed to this tag
|
/// subscribed to this tag
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public ICollection<string> subscripingApps {
|
public ICollection<string> subscripingApps
|
||||||
|
{
|
||||||
get { return mySubConnToApp.Values; }
|
get { return mySubConnToApp.Values; }
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -54,7 +57,8 @@ namespace BCG {
|
|||||||
/// connections as key and the application
|
/// connections as key and the application
|
||||||
/// as value
|
/// as value
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public Dictionary<string, string> subscribers {
|
public Dictionary<string, string> subscribers
|
||||||
|
{
|
||||||
get { return mySubConnToApp; }
|
get { return mySubConnToApp; }
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -62,7 +66,8 @@ namespace BCG {
|
|||||||
/// Returns a dictionary for all active publishing
|
/// Returns a dictionary for all active publishing
|
||||||
/// connection as key and the application as value
|
/// connection as key and the application as value
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public Dictionary<string, string> activePublishers {
|
public Dictionary<string, string> activePublishers
|
||||||
|
{
|
||||||
get { return myActivePubConnToApp; }
|
get { return myActivePubConnToApp; }
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -70,7 +75,8 @@ namespace BCG {
|
|||||||
/// Returns a list of pair of dead connections and
|
/// Returns a list of pair of dead connections and
|
||||||
/// theur corresponding applications
|
/// theur corresponding applications
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public ICollection<Pair<string, string>> deadPublishers {
|
public ICollection<Pair<string, string>> deadPublishers
|
||||||
|
{
|
||||||
get { return myDeadPubConnToApp; }
|
get { return myDeadPubConnToApp; }
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -81,14 +87,18 @@ namespace BCG {
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="sub"></param>
|
/// <param name="sub"></param>
|
||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
public bool onSub(SubMetaData sub) {
|
public bool onSub(SubMetaData sub)
|
||||||
if (mySubConnToApp.ContainsKey(sub.connection)) {
|
{
|
||||||
|
if (mySubConnToApp.ContainsKey(sub.connection))
|
||||||
|
{
|
||||||
string warningMsg = String.Format("tag {0} has already been subscribed by app {0}, subscriber {1}"
|
string warningMsg = String.Format("tag {0} has already been subscribed by app {0}, subscriber {1}"
|
||||||
+ "it should unsubscribe before resubscribing", myTag, sub.connection, sub.subscribing_app);
|
+ "it should unsubscribe before resubscribing", myTag, sub.connection, sub.subscribingApp);
|
||||||
//BlueEventLog.logEvent(warningMsg, BlueEventLevel.Warning);
|
//BlueEventLog.logEvent(warningMsg, BlueEventLevel.Warning);
|
||||||
return false;
|
return false;
|
||||||
} else {
|
}
|
||||||
mySubConnToApp.Add(sub.connection, sub.subscribing_app);
|
else
|
||||||
|
{
|
||||||
|
mySubConnToApp.Add(sub.connection, sub.subscribingApp);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -100,13 +110,17 @@ namespace BCG {
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="unsub"></param>
|
/// <param name="unsub"></param>
|
||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
public bool onUnsub(UnSubMetaData unsub) {
|
public bool onUnsub(UnSubMetaData unsub)
|
||||||
if (mySubConnToApp.ContainsKey(unsub.connection)) {
|
{
|
||||||
|
if (mySubConnToApp.ContainsKey(unsub.connection))
|
||||||
|
{
|
||||||
mySubConnToApp.Remove(unsub.connection);
|
mySubConnToApp.Remove(unsub.connection);
|
||||||
return true;
|
return true;
|
||||||
} else {
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
string warningMsg = String.Format("tag {0} has already was either unsubscribed or never subscribed by app {0}, subscriber {1}",
|
string warningMsg = String.Format("tag {0} has already was either unsubscribed or never subscribed by app {0}, subscriber {1}",
|
||||||
myTag, unsub.connection, unsub.unsubscribing_app);
|
myTag, unsub.connection, unsub.unsubscribingApp);
|
||||||
//BlueEventLog.logEvent(warningMsg, BlueEventLevel.Warning);
|
//BlueEventLog.logEvent(warningMsg, BlueEventLevel.Warning);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -119,20 +133,25 @@ namespace BCG {
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="subscriber"></param>
|
/// <param name="subscriber"></param>
|
||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
public bool onConnectionRemoveIfExists(string connection, out bool wasPublisher, out bool wasSubscriber) {
|
public bool onConnectionRemoveIfExists(string connection, out bool wasPublisher, out bool wasSubscriber)
|
||||||
|
{
|
||||||
bool rv = false;
|
bool rv = false;
|
||||||
if (mySubConnToApp.ContainsKey(connection)) {
|
if (mySubConnToApp.ContainsKey(connection))
|
||||||
|
{
|
||||||
mySubConnToApp.Remove(connection);
|
mySubConnToApp.Remove(connection);
|
||||||
wasSubscriber = true;
|
wasSubscriber = true;
|
||||||
rv = true;
|
rv = true;
|
||||||
} else
|
}
|
||||||
|
else
|
||||||
wasSubscriber = false;
|
wasSubscriber = false;
|
||||||
if (myActivePubConnToApp.ContainsKey(connection)) {
|
if (myActivePubConnToApp.ContainsKey(connection))
|
||||||
|
{
|
||||||
myDeadPubConnToApp.Add(new Pair<string, string>(connection, myActivePubConnToApp[connection]));
|
myDeadPubConnToApp.Add(new Pair<string, string>(connection, myActivePubConnToApp[connection]));
|
||||||
myActivePubConnToApp.Remove(connection);
|
myActivePubConnToApp.Remove(connection);
|
||||||
rv = true;
|
rv = true;
|
||||||
wasPublisher = true;
|
wasPublisher = true;
|
||||||
} else
|
}
|
||||||
|
else
|
||||||
wasPublisher = false;
|
wasPublisher = false;
|
||||||
return rv;
|
return rv;
|
||||||
}
|
}
|
||||||
@@ -143,10 +162,14 @@ namespace BCG {
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="pub"></param>
|
/// <param name="pub"></param>
|
||||||
/// <param name="publisherChanged"></param>
|
/// <param name="publisherChanged"></param>
|
||||||
public void onPub(PubMetaData pub, out bool newPublisher) {
|
public void onPub(PubMetaData pub, out bool newPublisher)
|
||||||
if (myActivePubConnToApp.ContainsKey(pub.connection)) {
|
{
|
||||||
|
if (myActivePubConnToApp.ContainsKey(pub.connection))
|
||||||
|
{
|
||||||
newPublisher = false;
|
newPublisher = false;
|
||||||
} else {
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
myActivePubConnToApp.Add(pub.connection, pub.publishingApp);
|
myActivePubConnToApp.Add(pub.connection, pub.publishingApp);
|
||||||
newPublisher = true;
|
newPublisher = true;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,79 +6,92 @@ using BCG.Utilities;
|
|||||||
using BCG.Utilities.General;
|
using BCG.Utilities.General;
|
||||||
using mbts;
|
using mbts;
|
||||||
using mbts.spark;
|
using mbts.spark;
|
||||||
using System.Linq;
|
|
||||||
|
|
||||||
namespace BCG {
|
namespace BCG {
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Deals with the subscriptions (and getting data) from one sole
|
/// Deals with the subscriptions (and getting data) from one
|
||||||
/// pubsub server. Theres no sharing of conenctions
|
/// pubsub server. Once again, theres no sharing of conenctions
|
||||||
/// from this point. Every new instance of SingleServerSubscriberCore
|
/// from this point. Every new instance of SingleServerSubscriberCore
|
||||||
/// will open its new dedicated connection to the serverURL that
|
/// will open its new dedicated connection to the serverURL that
|
||||||
/// its concerned with (passed as a conctructor arg).
|
/// its concerned with (passed as a conctructor arg).
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|
||||||
internal interface ISingleServerSubscriberCore : IDisposable {
|
internal interface ISingleServerSubscriberCore : IDisposable {
|
||||||
IList<PubSubUpdate> get_updates(int timeout);
|
IList<PubSubUpdate> getUpdates(int timeout);
|
||||||
event PubSubConnectionChange onPubSubConnectionChange;
|
event PubSubConnectionChange onPubSubConnectionChange;
|
||||||
void subscribe(params string[] tags);
|
void subscribe(params string[] tags);
|
||||||
void unsubscribe(params string[] tags);
|
void unsubscribe(params string[] tags);
|
||||||
}
|
}
|
||||||
|
|
||||||
internal class SingleServerSubscriberCore : IDisposable, ISingleServerSubscriberCore {
|
internal class SingleServerSubscriberCore : IDisposable, ISingleServerSubscriberCore {
|
||||||
private readonly IPubSubServerConnection _connection;
|
private HighPerformanceTimer myTimer = new HighPerformanceTimer();
|
||||||
private HashSet<string> _tags = new HashSet<string>();
|
private PubSubServerConnectionFactory myConnectionFactory;
|
||||||
|
private IPubSubServerConnection myConnection;
|
||||||
|
private List<string> myTags = new List<string>();
|
||||||
|
|
||||||
public event PubSubConnectionChange onPubSubConnectionChange;
|
public event PubSubConnectionChange onPubSubConnectionChange;
|
||||||
|
|
||||||
public SingleServerSubscriberCore(string server_url, PubSubServerConnectionFactory connection_factory) {
|
public SingleServerSubscriberCore(string serverURL, PubSubServerConnectionFactory connectionFactory) {
|
||||||
_connection = connection_factory.getConnection(server_url);
|
myConnectionFactory = connectionFactory;
|
||||||
_connection.register_connection_callback(new PubSubServerConnectionStatusChange(_handle_server_reboot));
|
myConnection = myConnectionFactory.getConnection(serverURL);
|
||||||
|
myConnection.registerCallback(new ConnectionStatusChange(myHandleServerReboot));
|
||||||
}
|
}
|
||||||
|
|
||||||
// gets as many data items that it can get in
|
public IList<PubSubUpdate> getUpdates() {
|
||||||
// timeout milliseconds
|
return getUpdates(1);
|
||||||
public IList<PubSubUpdate> get_updates(int timeout) {
|
}
|
||||||
_connection.doWork();
|
|
||||||
List<PubSubUpdate> rv = new List<PubSubUpdate>();
|
|
||||||
IList<byte[]> data_items = _connection.get_updates(new TimeSpan(0, 0, 0, 0, timeout), Int32.MaxValue);
|
|
||||||
foreach (byte[] data in data_items) {
|
|
||||||
process_message(data, ref rv);
|
|
||||||
}
|
|
||||||
return rv;
|
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// gets as many data items that it can get in
|
||||||
|
/// timeout milliseconds
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="timeout"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public IList<PubSubUpdate> getUpdates(int timeout) {
|
||||||
|
myConnection.doWork();
|
||||||
|
List<PubSubUpdate> retVal = new List<PubSubUpdate>();
|
||||||
|
IList<byte[]> dataItems = myConnection.getUpdates(new TimeSpan(0, 0, 0, 0, timeout), Int32.MaxValue);
|
||||||
|
foreach (byte[] data in dataItems) {
|
||||||
|
processMessage(data, retVal);
|
||||||
|
}
|
||||||
|
return retVal;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<string> getSubscribedTags() {
|
||||||
|
return myTags;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void subscribe(params string[] tags) {
|
public void subscribe(params string[] tags) {
|
||||||
HashSet<string> this_call_tags = new HashSet<string>();
|
|
||||||
foreach (string tag in tags) {
|
foreach (string tag in tags) {
|
||||||
if (_tags.Contains(tag)) {
|
if (myTags.Contains(tag)) {
|
||||||
bts.Log.Gen.warn($"SingleServerSubscriberCore.subscribe tag: {tag} has already " +
|
string errMsg = String.Format("tag {0} was already subscribed from server {1}",
|
||||||
$"been subscribed to server: {_connection.server_url}");
|
tag, myConnection.serverURL);
|
||||||
|
// BlueEventLog.logEvent(errMsg, BlueEventLevel.Warning);
|
||||||
} else {
|
} else {
|
||||||
_tags.Add(tag);
|
myTags.Add(tag);
|
||||||
this_call_tags.Add(tag);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
_subscribe(this_call_tags.ToArray());
|
mySubscribe(tags);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void unsubscribe(params string[] tags) {
|
public void unsubscribe(params string[] tags) {
|
||||||
List<string> unsub_tags = new List<string>();
|
List<string> unsubTags = new List<string>();
|
||||||
foreach (string tag in tags) {
|
foreach (string tag in tags) {
|
||||||
if (_tags.Contains(tag)) {
|
if (myTags.Contains(tag)) {
|
||||||
_tags.Remove(tag);
|
myTags.Remove(tag);
|
||||||
unsub_tags.Add(tag);
|
unsubTags.Add(tag);
|
||||||
} else {
|
} else {
|
||||||
bts.Log.Gen.warn($"tag {tag} is being unsubscribed from server {_connection.server_url}, eventhough " +
|
string errMsg = String.Format("tag {0} is being unsubscribed from server {1}, eventhough " +
|
||||||
"it was never subscribed");
|
"it was never subscribed", tag, myConnection.serverURL);
|
||||||
|
// BlueEventLog.logEvent(errMsg, BlueEventLevel.Warning);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
UnSubMetaData unsub = new UnSubMetaData(_connection.client_url, unsub_tags.ToArray());
|
UnSubMetaData unsub = new UnSubMetaData(myConnection.clientURL, unsubTags.ToArray());
|
||||||
Stream str = new MemoryStream();
|
Stream str = new MemoryStream();
|
||||||
new MetaData(unsub).save(str);
|
new MetaData(unsub).save(str);
|
||||||
str.Position = 0;
|
str.Position = 0;
|
||||||
// this is where data is actually sent out
|
// this is where data is actually sent out
|
||||||
_connection.send(str);
|
myConnection.send(str);
|
||||||
}
|
}
|
||||||
|
|
||||||
#region IDisposable Members
|
#region IDisposable Members
|
||||||
@@ -86,40 +99,44 @@ namespace BCG {
|
|||||||
public void Dispose() {
|
public void Dispose() {
|
||||||
///remove the subscriber
|
///remove the subscriber
|
||||||
//myConnection.send(new PubSubMessage(new MetaData(new RemoveSubscriberMetaData(myConnection.clientURL))).toStream());
|
//myConnection.send(new PubSubMessage(new MetaData(new RemoveSubscriberMetaData(myConnection.clientURL))).toStream());
|
||||||
_connection.Dispose();
|
myConnection.Dispose();
|
||||||
|
|
||||||
//clear the list of tags you've subscribed to. This way, if you're ever used again,
|
//clear the list of tags you've subscribed to. This way, if you're ever used again,
|
||||||
//you have to send all the subscription metadata out again.
|
//you have to send all the subscription metadata out again.
|
||||||
_tags.Clear();
|
myTags.Clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
// The only place where a tag is subsribed from the Pubsub server. the subscriptions are wrapped into SubMetaData,
|
private void mySubscribe(params string[] tags) {
|
||||||
// which has a to_stream method
|
SubMetaData sub = new SubMetaData(myConnection.clientURL, tags);
|
||||||
private void _subscribe(params string[] tags) {
|
|
||||||
SubMetaData sub = new SubMetaData(_connection.client_url, tags);
|
|
||||||
Stream str = new MemoryStream();
|
Stream str = new MemoryStream();
|
||||||
new MetaData(sub).save(str);
|
new MetaData(sub).save(str);
|
||||||
str.Position = 0;
|
str.Position = 0;
|
||||||
_connection.send(str);
|
myConnection.send(str);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void _handle_server_reboot(string server_url, bool connection) {
|
private void myHandleServerReboot(string serverURL, bool connection) {
|
||||||
bts.Log.Gen.info($"SingleServerSubscriberCore._handle_server_reboot connection: {connection} server_url: {server_url}");
|
|
||||||
if (connection) {
|
if (connection) {
|
||||||
_subscribe(_tags.ToArray());
|
//ie connection established again, so has to
|
||||||
|
//resubscribe to all the tags
|
||||||
|
// BlueEventLog.logEvent("Connection to " + serverURL + " established....resubscribing to all the tags",
|
||||||
|
// PS3LogLevels.disconnection);
|
||||||
|
mySubscribe(myTags.ToArray());
|
||||||
|
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
bts.Log.Gen.warn($"SingleServerSubscriberCore._handle_server_reboot connection to {server_url} broke....attempting to reconnect...");
|
// BlueEventLog.logEvent("Connection to " + serverURL + " broke....attempting to reconnect...",
|
||||||
|
// PS3LogLevels.disconnection);
|
||||||
}
|
}
|
||||||
|
|
||||||
//let anyone watching you know that your connection has changed.
|
//let anyone watching you know that your connection has changed.
|
||||||
if (onPubSubConnectionChange != null)
|
if (onPubSubConnectionChange != null)
|
||||||
onPubSubConnectionChange(connection, server_url);
|
onPubSubConnectionChange(connection, serverURL);
|
||||||
}
|
}
|
||||||
|
|
||||||
// processes one message
|
internal static void processMessage(byte[] data, List<PubSubUpdate> updates) {
|
||||||
internal static void process_message(byte[] data, ref List<PubSubUpdate> updates) {
|
bts.Log.Gen.info($"SingleServerSubscriberCore.processMessage data_len: {data.Length}");
|
||||||
bts.Log.Gen.info($"SingleServerSubscriberCore.process_message data_len: {data.Length}");
|
|
||||||
if (data != null && data.Length > 0) {
|
if (data != null && data.Length > 0) {
|
||||||
MemoryStream str = new MemoryStream(data);
|
MemoryStream str = new MemoryStream(data);
|
||||||
MetaData md = new MetaData(str);
|
MetaData md = new MetaData(str);
|
||||||
@@ -139,8 +156,9 @@ namespace BCG {
|
|||||||
if (p.isCompressed) {
|
if (p.isCompressed) {
|
||||||
long compressedSize = bodyStream.Length - bodyStream.Position;
|
long compressedSize = bodyStream.Length - bodyStream.Position;
|
||||||
if (compressedSize == 0) {
|
if (compressedSize == 0) {
|
||||||
string error_msg = "Error: tag " + p.tag + " had compressed size of 0 bytes. This shouldnt' happen. Not decompressing tag or sending out update.";
|
string errorMsg = "Error: tag " + p.tag + " had compressed size of 0 bytes. This shouldnt' happen. Not decompressing tag or sending out update.";
|
||||||
bts.Log.Gen.error(error_msg);
|
Console.WriteLine(errorMsg);
|
||||||
|
//BlueEventLog.logEvent(errorMsg, BlueEventLevel.Error);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
realBody = StreamDecompressor.decompressStream(bodyStream);
|
realBody = StreamDecompressor.decompressStream(bodyStream);
|
||||||
@@ -156,9 +174,8 @@ namespace BCG {
|
|||||||
bodyStream.Read(trConstituent, 0, trConstituent.Length);
|
bodyStream.Read(trConstituent, 0, trConstituent.Length);
|
||||||
updates.Add(new PubSubUpdate(pub.tag, pub.type, new MemoryStream(trConstituent)));
|
updates.Add(new PubSubUpdate(pub.tag, pub.type, new MemoryStream(trConstituent)));
|
||||||
}
|
}
|
||||||
} else {
|
} else { }
|
||||||
bts.Log.assert_d(false, $"Unknown message received: {md} only Pub and TransactionMetaData are legal");
|
//BlueEventLog.logAssert(false, "Unknown message received: " + md.ToString() + " , only Pub and TransactionMetaData are legal");
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -387,18 +387,18 @@ namespace BCG {
|
|||||||
SharpZipCompressor Kompressor = new SharpZipCompressor();
|
SharpZipCompressor Kompressor = new SharpZipCompressor();
|
||||||
Kompressor.compress(content, contentToPublish);
|
Kompressor.compress(content, contentToPublish);
|
||||||
contentToPublish.Position = 0;
|
contentToPublish.Position = 0;
|
||||||
pub_md = new PubMetaData(tagType, tag, (int)contentToPublish.Length, (int)content.Length, serverConn.server_url, ClientInfo.thisClient.processString);
|
pub_md = new PubMetaData(tagType, tag, (int)contentToPublish.Length, (int)content.Length, serverConn.serverURL, ClientInfo.thisClient.processString);
|
||||||
if (PubSub3Config.instance.shouldReportTagsCompressionRatio(tag)) {
|
if (PubSub3Config.instance.shouldReportTagsCompressionRatio(tag)) {
|
||||||
string msg = String.Format("Compressed tag {0} from {1} to {2} bytes, for a ratio of {3}.", tag, content.Length, contentToPublish.Length, (float)content.Length / (float)contentToPublish.Length);
|
string msg = String.Format("Compressed tag {0} from {1} to {2} bytes, for a ratio of {3}.", tag, content.Length, contentToPublish.Length, (float)content.Length / (float)contentToPublish.Length);
|
||||||
// BlueEventLog.logEvent(msg, PS3LogLevels.compressionRatio);
|
// BlueEventLog.logEvent(msg, PS3LogLevels.compressionRatio);
|
||||||
}
|
}
|
||||||
} else
|
} else
|
||||||
pub_md = new PubMetaData(tagType, tag, (int)content.Length, serverConn.server_url, ClientInfo.thisClient.processString);
|
pub_md = new PubMetaData(tagType, tag, (int)content.Length, serverConn.serverURL, ClientInfo.thisClient.processString);
|
||||||
|
|
||||||
content.Position = initPos;
|
content.Position = initPos;
|
||||||
if (CrushedTagManager.instance.is_tag_crushable(tag)) {
|
if (CrushedTagManager.instance.is_tag_crushable(tag)) {
|
||||||
string refId = String.Empty;
|
string refId = String.Empty;
|
||||||
Console.WriteLine("StaticPublisherCore.mySend serverConn.sendWithConfirmation tag: " + tag + " server: " + serverConn.server_url);
|
Console.WriteLine("StaticPublisherCore.mySend serverConn.sendWithConfirmation tag: " + tag + " server: " + serverConn.serverURL);
|
||||||
// this is where the data is actually sent out (there are multiple sends)
|
// this is where the data is actually sent out (there are multiple sends)
|
||||||
PubSubMessage ps_msg = new PubSubMessage(new MetaData(pub_md), contentToPublish);
|
PubSubMessage ps_msg = new PubSubMessage(new MetaData(pub_md), contentToPublish);
|
||||||
//Console.WriteLine($"serverConn.send_with_confirmation metadata: {ps_msg.meta_data}");
|
//Console.WriteLine($"serverConn.send_with_confirmation metadata: {ps_msg.meta_data}");
|
||||||
@@ -428,8 +428,8 @@ namespace BCG {
|
|||||||
myTagToSender.Add(tag, sender);
|
myTagToSender.Add(tag, sender);
|
||||||
if (!mySenders.Contains(sender)) {
|
if (!mySenders.Contains(sender)) {
|
||||||
mySenders.Add(sender);
|
mySenders.Add(sender);
|
||||||
sender.register_connection_callback(new PubSubServerConnectionStatusChange(myHandleServerReboot));
|
sender.registerCallback(new ConnectionStatusChange(myHandleServerReboot));
|
||||||
sender.register_send_confirmation_callback(new SendConfirmationDelegate(myOnSendConfirm));
|
sender.registerSendConfirmationCallback(new SendConfirmationDelegate(myOnSendConfirm));
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -443,7 +443,7 @@ namespace BCG {
|
|||||||
if (connection) {
|
if (connection) {
|
||||||
string thisPublisherInfo = ClientInfo.thisClient.processString;
|
string thisPublisherInfo = ClientInfo.thisClient.processString;
|
||||||
foreach (KeyValuePair<string, IPubSubServerConnection> p in myTagToSender) {
|
foreach (KeyValuePair<string, IPubSubServerConnection> p in myTagToSender) {
|
||||||
if (p.Value.server_url == serverURL) {
|
if (p.Value.serverURL == serverURL) {
|
||||||
string tagType;
|
string tagType;
|
||||||
string tag = p.Key;
|
string tag = p.Key;
|
||||||
myCrushableTagsWaitingOnAck.Clear();
|
myCrushableTagsWaitingOnAck.Clear();
|
||||||
|
|||||||
@@ -80,7 +80,7 @@ namespace BCG {
|
|||||||
int timeShare = timeout / myServerURLToSubCore.Count;
|
int timeShare = timeout / myServerURLToSubCore.Count;
|
||||||
List<PubSubUpdate> retVal = new List<PubSubUpdate>();
|
List<PubSubUpdate> retVal = new List<PubSubUpdate>();
|
||||||
foreach (ISingleServerSubscriberCore subCore in myServerURLToSubCore.Values)
|
foreach (ISingleServerSubscriberCore subCore in myServerURLToSubCore.Values)
|
||||||
retVal.AddRange(subCore.get_updates(timeShare));
|
retVal.AddRange(subCore.getUpdates(timeShare));
|
||||||
_log_n_updates(retVal);
|
_log_n_updates(retVal);
|
||||||
myTimer.stop();
|
myTimer.stop();
|
||||||
//if (myTimer.lastIntervalTime > 10)
|
//if (myTimer.lastIntervalTime > 10)
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ namespace BCG {
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
[STAThread]
|
[STAThread]
|
||||||
public static void Main(string[] args) {
|
public static void Main(string[] args) {
|
||||||
bool run_echo_tcp_server_only = false;
|
bool run_echo_tcp_server_only = true;
|
||||||
bts.utils.BTSNativeMethods.provide_command_line(new string[] { }, 0);
|
bts.utils.BTSNativeMethods.provide_command_line(new string[] { }, 0);
|
||||||
mbts.config.Configuration.initialize();
|
mbts.config.Configuration.initialize();
|
||||||
if (run_echo_tcp_server_only) {
|
if (run_echo_tcp_server_only) {
|
||||||
|
|||||||
@@ -8,7 +8,7 @@
|
|||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<OutputType>Exe</OutputType>
|
<OutputType>Exe</OutputType>
|
||||||
<TargetFramework>net8.0</TargetFramework>
|
<TargetFramework>net7.0</TargetFramework>
|
||||||
<Nullable>enable</Nullable>
|
<Nullable>enable</Nullable>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
|
|||||||
+65
-33
@@ -1,20 +1,26 @@
|
|||||||
namespace BCG {
|
namespace BCG
|
||||||
|
{
|
||||||
using bts.api.orderman;
|
using bts.api.orderman;
|
||||||
|
|
||||||
enum TSGroupByOperation {
|
enum TSGroupByOperation
|
||||||
|
{
|
||||||
OHLC = 0,
|
OHLC = 0,
|
||||||
Statistics
|
Statistics
|
||||||
}
|
}
|
||||||
|
|
||||||
[DataStreamUserRespresentation("timeseriesanalysis")]
|
[DataStreamUserRespresentation("timeseriesanalysis")]
|
||||||
public class TimeSeriesGroupByStream : ViewStream {
|
public class TimeSeriesGroupByStream : ViewStream
|
||||||
public TimeSeriesGroupByStream(ViewStream md_str, string timebucket_col, string output_col, string operation) {
|
{
|
||||||
|
public TimeSeriesGroupByStream(ViewStream md_str, string timebucket_col, string output_col, string operation)
|
||||||
|
{
|
||||||
ViewStream rs = new NonIncGroupByStream(md_str, new OHLCNonIncGroupByEvaluator(timebucket_col, output_col));
|
ViewStream rs = new NonIncGroupByStream(md_str, new OHLCNonIncGroupByEvaluator(timebucket_col, output_col));
|
||||||
mySetRealSource(rs);
|
mySetRealSource(rs);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
public class OHLC {
|
public class OHLC
|
||||||
public OHLC(double open_args, double high_args, double low_args, double close_args) {
|
{
|
||||||
|
public OHLC(double open_args, double high_args, double low_args, double close_args)
|
||||||
|
{
|
||||||
open = open_args;
|
open = open_args;
|
||||||
high = high_args;
|
high = high_args;
|
||||||
low = low_args;
|
low = low_args;
|
||||||
@@ -22,36 +28,47 @@ namespace BCG {
|
|||||||
}
|
}
|
||||||
public readonly double open, high, low, close;
|
public readonly double open, high, low, close;
|
||||||
}
|
}
|
||||||
public class OHLCNonIncGroupByEvaluator : INonIncGroupByEvaluator {
|
public class OHLCNonIncGroupByEvaluator : INonIncGroupByEvaluator
|
||||||
|
{
|
||||||
private string _timestamp_col;
|
private string _timestamp_col;
|
||||||
private string _output_col;
|
private string _output_col;
|
||||||
private IBlueTableSchema _out_schema;
|
private IBlueTableSchema _out_schema;
|
||||||
public OHLCNonIncGroupByEvaluator(string timestamp_col, string output_col) {
|
public OHLCNonIncGroupByEvaluator(string timestamp_col, string output_col)
|
||||||
|
{
|
||||||
_timestamp_col = timestamp_col;
|
_timestamp_col = timestamp_col;
|
||||||
_output_col = output_col;
|
_output_col = output_col;
|
||||||
}
|
}
|
||||||
|
|
||||||
string open_col {
|
string open_col
|
||||||
get {
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
return $"{_output_col}_open"; ;
|
return $"{_output_col}_open"; ;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
string high_col {
|
string high_col
|
||||||
get {
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
return $"{_output_col}_high"; ;
|
return $"{_output_col}_high"; ;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
string low_col {
|
string low_col
|
||||||
get {
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
return $"{_output_col}_low"; ;
|
return $"{_output_col}_low"; ;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
string close_col {
|
string close_col
|
||||||
get {
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
return $"{_output_col}_close"; ;
|
return $"{_output_col}_close"; ;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
private void _build_schema(IBlueTableSchema input_schema) {
|
private void _build_schema(IBlueTableSchema input_schema)
|
||||||
|
{
|
||||||
List<Tuple<Type, string>> new_cols = new List<Tuple<Type, string>>();
|
List<Tuple<Type, string>> new_cols = new List<Tuple<Type, string>>();
|
||||||
ColumnInfo ci = input_schema[_output_col];
|
ColumnInfo ci = input_schema[_output_col];
|
||||||
new_cols.Add(Tuple.Create(ci.colType, open_col));
|
new_cols.Add(Tuple.Create(ci.colType, open_col));
|
||||||
@@ -62,25 +79,32 @@ namespace BCG {
|
|||||||
_out_schema = BlueDataUtility.add_columns(input_schema, new_cols.ToArray());
|
_out_schema = BlueDataUtility.add_columns(input_schema, new_cols.ToArray());
|
||||||
}
|
}
|
||||||
|
|
||||||
public IBlueRowCollection evaluate(IDictionary<int, List<IBlueRow>> grouped_rows) {
|
public IBlueRowCollection evaluate(IDictionary<int, List<IBlueRow>> grouped_rows)
|
||||||
if (_out_schema == null) {
|
{
|
||||||
if (grouped_rows.Count > 0) {
|
if (_out_schema == null)
|
||||||
foreach (var kv in grouped_rows) {
|
{
|
||||||
|
if (grouped_rows.Count > 0)
|
||||||
|
{
|
||||||
|
foreach (var kv in grouped_rows)
|
||||||
|
{
|
||||||
var rows = kv.Value;
|
var rows = kv.Value;
|
||||||
if (rows != null && rows.Count > 0) {
|
if (rows != null && rows.Count > 0)
|
||||||
|
{
|
||||||
_build_schema(rows.ElementAt(0).schema);
|
_build_schema(rows.ElementAt(0).schema);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
BlueTable rv = new BlueTable(_out_schema);
|
BlueTable rv = new BlueTable(_out_schema);
|
||||||
foreach (var kv in grouped_rows) {
|
foreach (var kv in grouped_rows)
|
||||||
|
{
|
||||||
int id = kv.Key;
|
int id = kv.Key;
|
||||||
IEnumerable<IBlueRow> rows = kv.Value;
|
IEnumerable<IBlueRow> rows = kv.Value;
|
||||||
// sort rows on time to compute o,c correctly??
|
// sort rows on time to compute o,c correctly??
|
||||||
if (rows != null && rows.Count() > 0) {
|
if (rows != null && rows.Count() > 0)
|
||||||
|
{
|
||||||
IBlueRow reprow = rows.ElementAt(0);
|
IBlueRow reprow = rows.ElementAt(0);
|
||||||
IEnumerable<double> vals = BlueDataUtility.get_colvalues<double>(rows, _output_col);
|
IEnumerable<double> vals = BlueDataUtility.get_covalues<double>(rows, _output_col);
|
||||||
OHLC ohlc = compute_ohlc(vals);
|
OHLC ohlc = compute_ohlc(vals);
|
||||||
MutableBlueRow nr = new MutableBlueRow(_out_schema, reprow);
|
MutableBlueRow nr = new MutableBlueRow(_out_schema, reprow);
|
||||||
nr.setValue("id", kv.Key);
|
nr.setValue("id", kv.Key);
|
||||||
@@ -94,11 +118,13 @@ namespace BCG {
|
|||||||
return rv;
|
return rv;
|
||||||
}
|
}
|
||||||
|
|
||||||
public string[] get_group_by_cols() {
|
public string[] get_group_by_cols()
|
||||||
|
{
|
||||||
return new string[] { _timestamp_col };
|
return new string[] { _timestamp_col };
|
||||||
}
|
}
|
||||||
|
|
||||||
static OHLC compute_ohlc(IEnumerable<double> prices) {
|
static OHLC compute_ohlc(IEnumerable<double> prices)
|
||||||
|
{
|
||||||
double o = prices.ElementAt(0);
|
double o = prices.ElementAt(0);
|
||||||
double c = prices.ElementAt(prices.Count() - 1);
|
double c = prices.ElementAt(prices.Count() - 1);
|
||||||
double l = prices.Min();
|
double l = prices.Min();
|
||||||
@@ -107,22 +133,28 @@ namespace BCG {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
[DataStreamUserRespresentation("RSI")]
|
[DataStreamUserRespresentation("RSI")]
|
||||||
public class RSIStream : ViewStream {
|
public class RSIStream : ViewStream
|
||||||
|
{
|
||||||
// for ms_str expects a timeseries with a timestamp col and an output col (price col) to calculate the ohlc for
|
// for ms_str expects a timeseries with a timestamp col and an output col (price col) to calculate the ohlc for
|
||||||
public RSIStream(ViewStream md_str, string timebucket_column, string output_col) {
|
public RSIStream(ViewStream md_str, string timebucket_column, string output_col)
|
||||||
|
{
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
[DataStreamUserRespresentation("Bollinger")]
|
[DataStreamUserRespresentation("Bollinger")]
|
||||||
public class BollingerBandStream : ViewStream {
|
public class BollingerBandStream : ViewStream
|
||||||
|
{
|
||||||
// for ms_str expects a timeseries with a timestamp col and an output col (price col) to calculate the ohlc for
|
// for ms_str expects a timeseries with a timestamp col and an output col (price col) to calculate the ohlc for
|
||||||
public BollingerBandStream(ViewStream md_str, string timebucket_column, string output_col) {
|
public BollingerBandStream(ViewStream md_str, string timebucket_column, string output_col)
|
||||||
|
{
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
[DataStreamUserRespresentation("OHLC")]
|
[DataStreamUserRespresentation("OHLC")]
|
||||||
public class OHLCStream : ViewStream {
|
public class OHLCStream : ViewStream
|
||||||
|
{
|
||||||
// for ms_str expects a timeseries with a timestamp col and an output col (price col) to calculate the ohlc for
|
// for ms_str expects a timeseries with a timestamp col and an output col (price col) to calculate the ohlc for
|
||||||
public OHLCStream(ViewStream md_str, string timebucket_column, string output_col) {
|
public OHLCStream(ViewStream md_str, string timebucket_column, string output_col)
|
||||||
|
{
|
||||||
ViewStream rs = new TimeSeriesGroupByStream(md_str, timebucket_column, output_col, TSGroupByOperation.OHLC.ToString());
|
ViewStream rs = new TimeSeriesGroupByStream(md_str, timebucket_column, output_col, TSGroupByOperation.OHLC.ToString());
|
||||||
mySetRealSource(rs);
|
mySetRealSource(rs);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
<Project Sdk="Microsoft.NET.Sdk">
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<TargetFramework>net8.0</TargetFramework>
|
<TargetFramework>net7.0</TargetFramework>
|
||||||
<ImplicitUsings>enable</ImplicitUsings>
|
<ImplicitUsings>enable</ImplicitUsings>
|
||||||
<Nullable>enable</Nullable>
|
<Nullable>enable</Nullable>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
<Project Sdk="Microsoft.NET.Sdk">
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<TargetFramework>net8.0</TargetFramework>
|
<TargetFramework>net7.0</TargetFramework>
|
||||||
<ImplicitUsings>enable</ImplicitUsings>
|
<ImplicitUsings>enable</ImplicitUsings>
|
||||||
<Nullable>enable</Nullable>
|
<Nullable>enable</Nullable>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|||||||
Loaded 100 of 156 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user