From 00dc057ea5d9244f1df9457a316fd193c54dbfb0 Mon Sep 17 00:00:00 2001 From: Joel Klinghed Date: Tue, 21 Oct 2025 23:14:25 +0200 Subject: main: Split out main api/server part from main Add fake-api, sharing api/server implementation with main but fakes bluetooth events, to simplify working on the UI. --- src/fake_api.cc | 409 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 409 insertions(+) create mode 100644 src/fake_api.cc (limited to 'src/fake_api.cc') diff --git a/src/fake_api.cc b/src/fake_api.cc new file mode 100644 index 0000000..f61ac8f --- /dev/null +++ b/src/fake_api.cc @@ -0,0 +1,409 @@ +#include "args.hh" +#include "bt.hh" +#include "cfg.hh" +#include "config.h" +#include "http.hh" +#include "logger.hh" +#include "looper.hh" +#include "server.hh" +#include "signals.hh" +#include "websocket.hh" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifndef VERSION +# define VERSION "unknown" +#endif + +namespace { + +class FakeConfig : public cfg::Config { + public: + [[nodiscard]] + std::optional get( + std::string_view /* name */) const override { + return std::nullopt; + } +}; + +class FakeApi { + public: + virtual ~FakeApi() = default; + + [[nodiscard]] + virtual std::vector devices() = 0; + + virtual void update_adapter() = 0; + virtual void update_device(bt::Device& device) = 0; + virtual void update_player(bt::Player& player) = 0; + + protected: + FakeApi() = default; +}; + +class FakeAdapter : public bt::Adapter { + public: + explicit FakeAdapter(FakeApi& api) : api_(api) {} + + [[nodiscard]] + std::string const& address() const override { + return address_; + } + + [[nodiscard]] + std::string const& name() const override { + return name_; + } + + [[nodiscard]] + bool discoverable() const override { + return discoverable_; + } + + [[nodiscard]] + uint32_t discoverable_timeout_seconds() const override { + return discoverable_timeout_seconds_; + } + + [[nodiscard]] + bool pairable() const override { + return true; + } + + [[nodiscard]] + uint32_t pairable_timeout_seconds() const override { + return pairable_timeout_seconds_; + } + + [[nodiscard]] + bool pairing() const override { + return false; + } + + [[nodiscard]] + bool powered() const override { + return true; + } + + [[nodiscard]] + bool connectable() const override { + return true; + } + + [[nodiscard]] + std::vector devices() const override { + return api_.devices(); + } + + void set_discoverable(bool discoverable) override { + if (discoverable == discoverable_) + return; + + discoverable_ = discoverable; + api_.update_adapter(); + } + + void set_discoverable_timeout_seconds(uint32_t timeout) override { + if (discoverable_timeout_seconds_ == timeout) + return; + + discoverable_timeout_seconds_ = timeout; + api_.update_adapter(); + } + + void set_pairable_timeout_seconds(uint32_t timeout) override { + if (pairable_timeout_seconds_ == timeout) + return; + + pairable_timeout_seconds_ = timeout; + api_.update_adapter(); + } + + private: + FakeApi& api_; + std::string const address_{"00:11:22:33:44:66"}; + std::string const name_{"Fake adapter"}; + bool discoverable_{false}; + uint32_t discoverable_timeout_seconds_{180}; + uint32_t pairable_timeout_seconds_{0}; +}; + +class FakeDevice : public bt::Device { + public: + FakeDevice(FakeApi& api, std::string address, std::string name) + : api_(api), address_(std::move(address)), name_(std::move(name)) {} + + [[nodiscard]] + std::string const& address() const override { + return address_; + } + + [[nodiscard]] + std::string const& name() const override { + return name_; + } + + [[nodiscard]] + bool paired() const override { + return paired_; + } + + [[nodiscard]] + bool connected() const override { + return player_ != nullptr; + } + + // Returns null if there are no players. + // Otherwise, returns the "primary" player. + [[nodiscard]] + bt::Player* player() const override { + return player_; + } + + void set_paired(bool paired) { + if (paired_ == paired) + return; + + paired_ = paired; + api_.update_device(*this); + } + + void set_player(bt::Player* player) { + if (player_ == player) + return; + + player_ = player; + api_.update_device(*this); + } + + private: + FakeApi& api_; + std::string const address_; + std::string const name_; + bool paired_{false}; + bt::Player* player_{nullptr}; +}; + +class FakePlayer : public bt::Player { + public: + explicit FakePlayer(FakeApi& api) : api_(api) {} + + [[nodiscard]] + Status status() const override { + return status_; + } + + [[nodiscard]] + std::string const& track_title() const override { + return title_; + } + + [[nodiscard]] + std::string const& track_artist() const override { + return artist_; + } + + [[nodiscard]] + std::string const& track_album() const override { + return album_; + } + + void play() override { + if (status_ == Status::kPlaying) + return; + status_ = Status::kPlaying; + api_.update_player(*this); + } + + void pause() override { + if (status_ == Status::kPaused) + return; + status_ = Status::kPaused; + api_.update_player(*this); + } + + void set(std::string const& title, std::string const& artist, + std::string const& album) { + if (title_ == title && artist_ == artist && album_ == album) + return; + title_ = title; + artist_ = artist; + album_ = album; + api_.update_player(*this); + } + + private: + FakeApi& api_; + Status status_{Status::kStopped}; + std::string title_; + std::string artist_; + std::string album_; +}; + +class FakeBluetoothManager : public FakeApi { + public: + FakeBluetoothManager(looper::Looper& looper, bt::Manager::Delegate& delegate) + : looper_(looper), delegate_(delegate) { + devices_.emplace_back(std::make_unique( + *this, "11:22:33:44:55:66", "Fake device 1")); + devices_.emplace_back(std::make_unique( + *this, "66:55:44:33:22:11", "Fake device 2")); + + players_.emplace_back(std::make_unique(*this)); + players_.emplace_back(std::make_unique(*this)); + + delegate_.new_adapter(&adapter_); + + schedule_next(); + } + + ~FakeBluetoothManager() override { + if (timer_) + looper_.cancel(timer_); + } + + std::vector devices() override { + std::vector ret; + std::ranges::copy(std::views::transform( + devices_, [](auto& device) { return device.get(); }), + std::back_inserter(ret)); + return ret; + } + + void update_adapter() override { delegate_.updated_adapter(adapter_); } + + void update_device(bt::Device& device) override { + delegate_.updated_device(device); + } + + void update_player(bt::Player& player) override { + auto it = std::ranges::find_if(devices_, [&player](auto& device) { + return device->player() == &player; + }); + if (it != devices_.end()) { + delegate_.updated_player(**it, player); + } + } + + private: + void schedule_next() { + timer_ = looper_.schedule(5, [this](uint32_t /* timer */) { + step(); + schedule_next(); + }); + } + + void step() { + switch (step_++) { + case 0: + devices_[0]->set_paired(true); + break; + case 1: + devices_[1]->set_paired(true); + break; + case 2: + devices_[1]->set_player(players_[1].get()); + delegate_.added_player(*devices_[1], *players_[1]); + break; + case 3: + players_[1]->play(); + break; + case 4: + players_[1]->pause(); + break; + case 5: + devices_[1]->set_player(nullptr); + delegate_.removed_player(*devices_[1]); + break; + case 6: + devices_[1]->set_paired(false); + break; + case 7: + devices_[0]->set_paired(false); + break; + default: + step_ = 0; + break; + } + } + + FakeAdapter adapter_{*this}; + std::vector> devices_; + std::vector> players_; + looper::Looper& looper_; + bt::Manager::Delegate& delegate_; + uint32_t timer_{0}; + uint32_t step_{0}; +}; + +bool run(logger::Logger& logger, std::unique_ptr port) { + FakeConfig cfg; + auto looper = looper::create(); + auto signaler = server::create_signaler(logger, cfg, *looper); + auto bt_delegate = server::create_bt_delegate(logger, *signaler); + auto http_delegate = server::create_http_delegate(*bt_delegate, *signaler); + auto server = http::create_server(logger, cfg, *looper, std::move(port), + *http_delegate); + FakeBluetoothManager fake_bt(*looper, *bt_delegate); + auto sigint_handler = signals::Handler::create( + *looper, signals::Signal::INT, [&looper, &logger]() { + logger.info("Received SIGINT, quitting..."); + looper->quit(); + }); + auto sigterm_handler = signals::Handler::create( + *looper, signals::Signal::TERM, [&looper, &logger]() { + logger.info("Received SIGTERM, quitting..."); + looper->quit(); + }); + return looper->run(logger); +} + +} // namespace + +int main(int argc, char** argv) { + auto args = Args::create(); + auto opt_help = args->option('h', "help", "display this text and exit."); + auto opt_version = args->option('V', "version", "display version and exit."); + auto opt_bind = + args->option_argument('B', "bind", "HOST:PORT", + "HOST:PORT to bind to, defaults to localhost:5555"); + if (!args->run(argc, argv)) { + args->print_error(std::cerr); + std::cerr << "Try 'fake-api --help' for more information.\n"; + return 1; + } + if (opt_help->is_set()) { + std::cout << "Usage: fake-api [OPTION...]\n" + << "\n"; + args->print_help(std::cout); + return 0; + } + if (opt_version->is_set()) { + std::cout << "bluetooth-jukebox " << VERSION + << " written by Joel Klinghed .\n"; + return 0; + } + + auto logger = logger::stderr(/* verbose */ true); + + std::string_view bind = "localhost:5555"; + if (opt_bind->is_set()) + bind = opt_bind->argument(); + + auto port = http::open_port(bind, *logger); + if (!port) + return 1; + + return run(*logger, std::move(port)) ? 0 : 1; +} -- cgit v1.2.3-70-g09d2