Compare commits

..
156 changed files with 2924 additions and 8499 deletions

No files matched your search

+16 -30
View File
@@ -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
View File
@@ -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
View File
@@ -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"
} }
}, },
+13 -29
View File
@@ -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);
} }
}
@@ -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,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>
@@ -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
###
@@ -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);
}
}
}
@@ -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, '&amp;')
.replace(/"/g, '&quot;')
.replace(/'/g, '&#39;')
.replace(/</g, '&lt;')
.replace(/>/g, '&gt;');
}
</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, '&amp;')
.replace(/"/g, '&quot;')
.replace(/'/g, '&#39;')
.replace(/</g, '&lt;')
.replace(/>/g, '&gt;');
}
</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>
-6
View File
@@ -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);
} }
} }
@@ -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
} }
+1 -1
View File
@@ -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>
+5 -8
View File
@@ -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)
+1 -1
View File
@@ -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>
@@ -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