musikcube/src/musikcubed/main.cpp
2023-02-05 14:05:56 -08:00

308 lines
8.1 KiB
C++

#ifdef EV_ERROR
#undef EV_ERROR
#endif
#include <iostream>
#include <fstream>
#include <csignal>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <signal.h>
#include <ev++.h>
#include <musikcore/audio/PlaybackService.h>
#include <musikcore/audio/MasterTransport.h>
#include <musikcore/debug.h>
#include <musikcore/library/LibraryFactory.h>
#include <musikcore/plugin/Plugins.h>
#include <musikcore/runtime/MessageQueue.h>
#include <musikcore/runtime/Message.h>
#include <musikcore/support/PreferenceKeys.h>
#include <musikcore/support/Common.h>
#include "../musikcore/sdk/version.h"
using namespace musik;
using namespace musik::core;
using namespace musik::core::audio;
using namespace musik::core::runtime;
static const char* DEFAULT_LOCKFILE = "/tmp/musikcubed.lock";
static const char* LOCKFILE_OVERRIDE = "MUSIKCUBED_LOCKFILE_OVERRIDE";
static const short EVENT_DISPATCH = 1;
static const short EVENT_QUIT = 2;
static const pid_t NOT_RUNNING = (pid_t) -1;
static int pipeFd[2] = { 0 };
static bool foreground = false;
static void printHelp();
static void handleCommandLine(int argc, char** argv);
static void exitIfRunning();
static pid_t getDaemonPid();
static void initForeground();
static void initDaemon();
static void stopDaemon();
static void initUtf8();
static void run();
class EvMessageQueue: public MessageQueue {
public:
void Post(IMessagePtr message, int64_t delayMs) {
MessageQueue::Post(message, delayMs);
if (delayMs <= 0) {
write(pipeFd[1], &EVENT_DISPATCH, sizeof(EVENT_DISPATCH));
}
else {
double delayTs = (double) delayMs / 1000.0;
loop.once<
EvMessageQueue,
&EvMessageQueue::DelayedDispatch
>(-1, ev::TIMER, (ev::tstamp) delayTs, this);
}
}
void DelayedDispatch(int revents) {
this->Dispatch();
}
static void SignalQuit(ev::sig& signal, int revents) {
write(pipeFd[1], &EVENT_QUIT, sizeof(EVENT_QUIT));
}
void ReadCallback(ev::io& watcher, int revents) {
short type;
if (read(pipeFd[0], &type, sizeof(type)) == 0) {
std::cerr << "read() failed.\n";
exit(EXIT_FAILURE);
}
switch (type) {
case EVENT_DISPATCH: this->Dispatch(); break;
case EVENT_QUIT: loop.break_loop(ev::ALL); break;
}
}
void Run() {
io.set(loop);
io.set(pipeFd[0], ev::READ);
io.set<EvMessageQueue, &EvMessageQueue::ReadCallback>(this);
io.start();
sio.set(loop);
sio.set<&EvMessageQueue::SignalQuit>();
sio.start(SIGTERM);
write(pipeFd[1], &EVENT_DISPATCH, sizeof(EVENT_DISPATCH));
loop.run(0);
}
private:
ev::dynamic_loop loop;
ev::io io;
ev::sig sio;
};
static void printHelp() {
std::cout << "\n musikcubed:\n";
std::cout << " --start: start the daemon\n";
std::cout << " --foreground: start the in the foreground\n";
std::cout << " --stop: shut down the daemon\n";
std::cout << " --running: check if the daemon is running\n";
std::cout << " --version: print the version\n";
std::cout << " --help: show this message\n\n";
}
static void handleCommandLine(int argc, char** argv) {
if (argc >= 2) {
const std::string command = std::string(argv[1]);
if (command == "--start") {
return;
}
else if (command == "--foreground") {
std::cout << "\n musikcubed starting in the foreground...\n\n";
::foreground = true;
return;
}
else if (command == "--stop") {
stopDaemon();
}
else if (command == "--version") {
std::cout << "\n musikcubed version: " << MUSIKCUBE_VERSION << " " << MUSIKCUBE_VERSION_COMMIT_HASH << "\n\n";
}
else if (command == "--running") {
pid_t pid = getDaemonPid();
if (pid == NOT_RUNNING) {
std::cout << "\n musikcubed is NOT running\n\n";
}
else {
std::cout << "\n musikcubed is running with pid " << pid << "\n\n";
}
}
else {
printHelp();
}
exit(EXIT_SUCCESS);
}
}
static std::string getLockfileFn() {
std::string result = DEFAULT_LOCKFILE;
const char* userLock = std::getenv(LOCKFILE_OVERRIDE);
if (userLock && strlen(userLock)) {
result = userLock;
}
return result;
}
static void stopDaemon() {
pid_t pid = getDaemonPid();
if (pid == NOT_RUNNING) {
std::cout << "\n musikcubed is not running\n\n";
}
else {
std::cout << "\n stopping musikcubed...";
kill(pid, SIGTERM);
int count = 0;
bool dead = false;
while (!dead && count++ < 7) { /* try for 7 seconds */
if (kill(pid, 0) == 0) {
std::cout << ".";
std::cout.flush();
usleep(500000);
}
else {
dead = true;
}
}
std::cout << (dead ? " success" : " failed") << "\n\n";
if (!dead) {
exit(EXIT_FAILURE);
}
}
}
static pid_t getDaemonPid() {
std::ifstream lock(getLockfileFn());
if (lock.good()) {
int pid;
lock >> pid;
if (kill((pid_t) pid, 0) == 0) {
return pid;
}
}
return NOT_RUNNING;
}
static void exitIfRunning() {
if (getDaemonPid() != NOT_RUNNING) {
std::cerr << "\n musikcubed is already running!\n\n";
exit(EXIT_SUCCESS);
}
std::cerr << "\n musikcubed is starting...\n\n";
}
static void initDaemon() {
pid_t pid = fork();
if (pid < 0) {
exit(EXIT_FAILURE);
}
if (pid > 0) {
exit(EXIT_SUCCESS);
}
umask(0);
pid_t sid = setsid();
if (sid < 0) {
exit(EXIT_SUCCESS);
}
if (chdir("/") < 0) {
exit(EXIT_FAILURE);
}
if (pipe(pipeFd) != 0) {
std::cerr << "\n ERROR! couldn't create pipe\n\n";
exit(EXIT_FAILURE);
}
close(STDIN_FILENO);
close(STDOUT_FILENO);
freopen("/tmp/musikcube.log", "w", stderr);
std::ofstream lock(getLockfileFn());
if (lock.good()) {
lock << std::to_string((int) getpid());
}
debug::Start({
new debug::SimpleFileBackend()
});
}
static void initForeground() {
if (pipe(pipeFd) != 0) {
std::cerr << "\n ERROR! couldn't create pipe\n\n";
exit(EXIT_FAILURE);
}
std::ofstream lock(getLockfileFn());
if (lock.good()) {
lock << std::to_string((int) getpid());
}
debug::Start({
new debug::ConsoleBackend(),
new debug::SimpleFileBackend()
});
}
static void rescanHandler(int signal) {
debug::info("daemon", "received SIGUSR1, rescanning the library...");
auto library = LibraryFactory::Instance().DefaultLocalLibrary();
library->Indexer()->Schedule(IIndexer::SyncType::All);
}
int main(int argc, char** argv) {
std::cout << "\n using lockfile at: " << getLockfileFn();
handleCommandLine(argc, argv);
exitIfRunning();
::foreground ? initForeground() : initDaemon();
srand((unsigned int) time(0));
std::signal(SIGUSR1, rescanHandler);
plugin::Init();
EvMessageQueue messageQueue;
LibraryFactory::Initialize(messageQueue);
auto library = LibraryFactory::Instance().DefaultLocalLibrary();
{
PlaybackService playback(messageQueue, library);
plugin::Start(&messageQueue, &playback, library);
auto prefs = Preferences::ForComponent(prefs::components::Settings);
if (prefs->GetBool(prefs::keys::SyncOnStartup, true)) {
library->Indexer()->Schedule(IIndexer::SyncType::All);
}
messageQueue.Run();
library->Indexer()->Shutdown();
}
LibraryFactory::Instance().Shutdown();
plugin::Shutdown();
debug::Shutdown();
remove(getLockfileFn().c_str());
}