warp-pipe/examples/warppipe_cli.cpp
2026-01-29 21:13:18 -07:00

304 lines
9.1 KiB
C++

#include <csignal>
#include <cstdint>
#include <cstdlib>
#include <iostream>
#include <string>
#include <warppipe/warppipe.hpp>
namespace {
volatile sig_atomic_t g_running = 1;
void SignalHandler(int) {
g_running = 0;
}
uint32_t ParseId(const char* value) {
if (!value) {
return 0;
}
char* end = nullptr;
unsigned long parsed = std::strtoul(value, &end, 10);
if (!end || end == value) {
return 0;
}
return static_cast<uint32_t>(parsed);
}
std::string ArgValue(int argc, char* argv[], int i, const char* flag) {
if (i + 1 < argc) {
return argv[i + 1];
}
std::cerr << "warppipe: missing value for " << flag << "\n";
return "";
}
int Usage() {
std::cerr << "Usage:\n"
<< " warppipe_cli list-nodes\n"
<< " warppipe_cli list-ports <node-id>\n"
<< " warppipe_cli list-links\n"
<< " warppipe_cli list-rules\n"
<< " warppipe_cli create-sink <name> [--rate N] [--channels N]\n"
<< " warppipe_cli create-source <name> [--rate N] [--channels N]\n"
<< " warppipe_cli link <out-node> <out-port> <in-node> <in-port> [--passive] [--linger]\n"
<< " warppipe_cli unlink <link-id>\n"
<< " warppipe_cli add-rule --app <name> --target <node> [--process <bin>] [--role <role>]\n"
<< " warppipe_cli remove-rule <rule-id>\n"
<< " warppipe_cli save-config <path>\n"
<< " warppipe_cli load-config <path>\n"
<< " warppipe_cli defaults\n";
return 2;
}
} // namespace
int main(int argc, char* argv[]) {
if (argc < 2) {
return Usage();
}
std::string command = argv[1];
warppipe::ConnectionOptions options;
options.application_name = "warppipe-cli";
auto client_result = warppipe::Client::Create(options);
if (!client_result.ok()) {
std::cerr << "warppipe: failed to connect: "
<< client_result.status.message << "\n";
return 1;
}
if (command == "list-nodes") {
auto nodes = client_result.value->ListNodes();
if (!nodes.ok()) {
std::cerr << "warppipe: list-nodes failed: " << nodes.status.message << "\n";
return 1;
}
for (const auto& node : nodes.value) {
std::cout << node.id.value << "\t" << node.name << "\t" << node.media_class << "\n";
}
return 0;
}
if (command == "list-ports") {
if (argc < 3) {
return Usage();
}
uint32_t node_id = ParseId(argv[2]);
if (node_id == 0) {
std::cerr << "warppipe: invalid node id\n";
return 1;
}
auto ports = client_result.value->ListPorts(warppipe::NodeId{node_id});
if (!ports.ok()) {
std::cerr << "warppipe: list-ports failed: " << ports.status.message << "\n";
return 1;
}
for (const auto& port : ports.value) {
std::cout << port.id.value << "\t" << port.name << "\t"
<< (port.is_input ? "in" : "out") << "\n";
}
return 0;
}
if (command == "list-links") {
auto links = client_result.value->ListLinks();
if (!links.ok()) {
std::cerr << "warppipe: list-links failed: " << links.status.message << "\n";
return 1;
}
for (const auto& link : links.value) {
std::cout << link.id.value << "\t" << link.output_port.value << "\t"
<< link.input_port.value << "\n";
}
return 0;
}
if (command == "list-rules") {
auto rules = client_result.value->ListRouteRules();
if (!rules.ok()) {
std::cerr << "warppipe: list-rules failed: " << rules.status.message << "\n";
return 1;
}
for (const auto& rule : rules.value) {
std::cout << rule.id.value << "\t"
<< "app=" << rule.match.application_name << "\t"
<< "proc=" << rule.match.process_binary << "\t"
<< "role=" << rule.match.media_role << "\t"
<< "-> " << rule.target_node << "\n";
}
return 0;
}
if (command == "create-sink" || command == "create-source") {
if (argc < 3) {
return Usage();
}
std::string name = argv[2];
warppipe::VirtualNodeOptions node_options;
for (int i = 3; i < argc; ++i) {
std::string arg = argv[i];
if (arg == "--rate" && i + 1 < argc) {
node_options.format.rate = ParseId(argv[++i]);
} else if (arg == "--channels" && i + 1 < argc) {
node_options.format.channels = ParseId(argv[++i]);
}
}
if (command == "create-sink") {
auto result = client_result.value->CreateVirtualSink(name, node_options);
if (!result.ok()) {
std::cerr << "warppipe: create-sink failed: " << result.status.message << "\n";
return 1;
}
std::cout << "created sink " << result.value.name
<< " (node " << result.value.node.value << ")\n";
} else {
auto result = client_result.value->CreateVirtualSource(name, node_options);
if (!result.ok()) {
std::cerr << "warppipe: create-source failed: " << result.status.message << "\n";
return 1;
}
std::cout << "created source " << result.value.name
<< " (node " << result.value.node.value << ")\n";
}
std::cout << "press Ctrl+C to stop\n";
std::signal(SIGINT, SignalHandler);
std::signal(SIGTERM, SignalHandler);
while (g_running) {
usleep(100000);
}
std::cout << "\nshutting down\n";
return 0;
}
if (command == "link") {
if (argc < 6) {
std::cerr << "warppipe: link requires <out-node> <out-port> <in-node> <in-port>\n";
return Usage();
}
std::string out_node = argv[2];
std::string out_port = argv[3];
std::string in_node = argv[4];
std::string in_port = argv[5];
warppipe::LinkOptions link_opts;
for (int i = 6; i < argc; ++i) {
std::string arg = argv[i];
if (arg == "--passive") {
link_opts.passive = true;
} else if (arg == "--linger") {
link_opts.linger = true;
}
}
auto result = client_result.value->CreateLinkByName(out_node, out_port, in_node, in_port, link_opts);
if (!result.ok()) {
std::cerr << "warppipe: link failed: " << result.status.message << "\n";
return 1;
}
std::cout << "created link " << result.value.id.value << "\n";
return 0;
}
if (command == "unlink") {
if (argc < 3) {
return Usage();
}
uint32_t link_id = ParseId(argv[2]);
if (link_id == 0) {
std::cerr << "warppipe: invalid link id\n";
return 1;
}
auto status = client_result.value->RemoveLink(warppipe::LinkId{link_id});
if (!status.ok()) {
std::cerr << "warppipe: unlink failed: " << status.message << "\n";
return 1;
}
std::cout << "removed link " << link_id << "\n";
return 0;
}
if (command == "add-rule") {
warppipe::RouteRule rule;
for (int i = 2; i < argc; ++i) {
std::string arg = argv[i];
if (arg == "--app" && i + 1 < argc) {
rule.match.application_name = argv[++i];
} else if (arg == "--process" && i + 1 < argc) {
rule.match.process_binary = argv[++i];
} else if (arg == "--role" && i + 1 < argc) {
rule.match.media_role = argv[++i];
} else if (arg == "--target" && i + 1 < argc) {
rule.target_node = argv[++i];
}
}
auto result = client_result.value->AddRouteRule(rule);
if (!result.ok()) {
std::cerr << "warppipe: add-rule failed: " << result.status.message << "\n";
return 1;
}
std::cout << "added rule " << result.value.value << "\n";
return 0;
}
if (command == "remove-rule") {
if (argc < 3) {
return Usage();
}
uint32_t rule_id = ParseId(argv[2]);
if (rule_id == 0) {
std::cerr << "warppipe: invalid rule id\n";
return 1;
}
auto status = client_result.value->RemoveRouteRule(warppipe::RuleId{rule_id});
if (!status.ok()) {
std::cerr << "warppipe: remove-rule failed: " << status.message << "\n";
return 1;
}
std::cout << "removed rule " << rule_id << "\n";
return 0;
}
if (command == "save-config") {
if (argc < 3) {
return Usage();
}
auto status = client_result.value->SaveConfig(argv[2]);
if (!status.ok()) {
std::cerr << "warppipe: save-config failed: " << status.message << "\n";
return 1;
}
std::cout << "saved config to " << argv[2] << "\n";
return 0;
}
if (command == "load-config") {
if (argc < 3) {
return Usage();
}
auto status = client_result.value->LoadConfig(argv[2]);
if (!status.ok()) {
std::cerr << "warppipe: load-config failed: " << status.message << "\n";
return 1;
}
std::cout << "loaded config from " << argv[2] << "\n";
return 0;
}
if (command == "defaults") {
auto defaults = client_result.value->GetDefaults();
if (!defaults.ok()) {
std::cerr << "warppipe: defaults failed: " << defaults.status.message << "\n";
return 1;
}
std::cout << "default_sink\t" << defaults.value.default_sink_name << "\n"
<< "default_source\t" << defaults.value.default_source_name << "\n"
<< "configured_sink\t" << defaults.value.configured_sink_name << "\n"
<< "configured_source\t" << defaults.value.configured_source_name << "\n";
return 0;
}
return Usage();
}