summaryrefslogtreecommitdiff
path: root/src/main.cc
blob: ff05a55c92c68d39b29e91005f2a2dd0460dfbea (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
#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 <cerrno>
#include <cstring>
#include <format>
#include <functional>
#include <iostream>
#include <memory>
#include <optional>
#include <string>
#include <string_view>
#include <unistd.h>
#include <utility>
#include <vector>

#ifndef VERSION
# define VERSION "unknown"
#endif

namespace {

bool run(logger::Logger& logger, cfg::Config const& cfg,
         std::unique_ptr<http::OpenPort> port) {
  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);
  auto manager = bt::create_manager(logger, cfg, *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);
}

bool start_daemon(logger::Logger& logger, cfg::Config const& cfg,
                  std::unique_ptr<http::OpenPort> port, bool verbose) {
  auto pid = fork();
  if (pid == 0) {
    // Child process
    // Move out of current workind directory to not keep it alive
    chdir("/");
    // Create a new process group
    setpgid(0, 0);
    // Close stdin, stdout and stderr
    close(0);
    close(1);
    close(2);
    // Use syslog logger
    auto logger2 = logger::syslog("bluetooth-jukebox", verbose);
    _exit(run(*logger2, cfg, std::move(port)) ? 0 : 1);
  } else if (pid == -1) {
    // Error
    logger.err(std::format("Unable to fork: {}", strerror(errno)));
    return false;
  } else {
    // Parent process
    logger.info(std::format("Forked into pid {}", pid));
    return true;
  }
}

}  // 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_config = args->option_argument(
      'C', "config", "FILE", "load config from FILE instead of default.");
  auto opt_verbose = args->option('v', "verbose", "be more verbose.");
  auto opt_daemon = args->option('d', "daemon", "fork into background.");
  if (!args->run(argc, argv)) {
    args->print_error(std::cerr);
    std::cerr << "Try 'bluetooth-jukebox --help' for more information.\n";
    return 1;
  }
  if (opt_help->is_set()) {
    std::cout << "Usage: bluetooth-jukebox [OPTION...]\n"
              << "\n";
    args->print_help(std::cout);
    return 0;
  }
  if (opt_version->is_set()) {
    std::cout << "bluetooth-jukebox " << VERSION
              << " written by Joel Klinghed <the_jk@spawned.biz>.\n";
    return 0;
  }
  bool verbose = opt_verbose->is_set();
  bool daemon = opt_daemon->is_set();
  std::unique_ptr<cfg::Config> cfg;
  std::vector<std::string> errors;
  if (opt_config->is_set()) {
    cfg = cfg::load_one(opt_config->argument(), errors);
  } else {
    cfg = cfg::load_all("bluetooth-jukebox", errors);
  }

  auto logger = logger::stderr(verbose);

  // Open the port here, in case we fork() later, to fail early.
  auto port = http::open_port(cfg->get("http.bind").value_or("localhost:5555"),
                              *logger);
  if (!port)
    return 1;

  if (daemon)
    return start_daemon(*logger, *cfg, std::move(port), verbose) ? 0 : 1;

  return run(*logger, *cfg, std::move(port)) ? 0 : 1;
}