237 lines
6.5 KiB
C++
237 lines
6.5 KiB
C++
#include "media_server_app.h"
|
|
|
|
#include <iostream>
|
|
#include <signal.h>
|
|
#include <string>
|
|
#include <cstdlib>
|
|
#include <atomic>
|
|
#include <thread>
|
|
#include <chrono>
|
|
#include <vector>
|
|
|
|
#if __has_include(<filesystem>)
|
|
#include <filesystem>
|
|
namespace fs = std::filesystem;
|
|
#endif
|
|
|
|
#if defined(__linux__)
|
|
#include <limits.h>
|
|
#include <unistd.h>
|
|
#include <sys/inotify.h>
|
|
#include <fcntl.h>
|
|
#endif
|
|
|
|
#include "graph_manager.h"
|
|
#include "http_server.h"
|
|
#include "utils/simple_json.h"
|
|
|
|
namespace rk3588 {
|
|
|
|
namespace {
|
|
|
|
GraphManager* g_manager = nullptr;
|
|
|
|
std::string ResolvePluginDir() {
|
|
if (const char* env = std::getenv("RK_PLUGIN_DIR")) {
|
|
return std::string(env);
|
|
}
|
|
|
|
#if defined(__linux__)
|
|
char path[PATH_MAX] = {0};
|
|
ssize_t len = readlink("/proc/self/exe", path, sizeof(path) - 1);
|
|
if (len > 0) {
|
|
std::string exe_path(path, static_cast<size_t>(len));
|
|
auto pos = exe_path.find_last_of('/');
|
|
if (pos != std::string::npos) {
|
|
return exe_path.substr(0, pos) + "/plugins";
|
|
}
|
|
}
|
|
#endif
|
|
|
|
return std::string("plugins");
|
|
}
|
|
|
|
void HandleSignal(int) {
|
|
if (g_manager) {
|
|
std::cerr << "\n[MediaServerApp] Caught signal, stopping...\n";
|
|
g_manager->RequestStop();
|
|
}
|
|
}
|
|
|
|
bool HotReloadEnabled(const SimpleJson& root_cfg) {
|
|
if (const SimpleJson* g = root_cfg.Find("global")) {
|
|
if (const SimpleJson* hr = g->Find("hot_reload")) {
|
|
return hr->ValueOr<bool>("enable", false);
|
|
}
|
|
// Backward compatible: allow global.hot_reload_enable
|
|
if (g->Find("hot_reload_enable")) {
|
|
return g->ValueOr<bool>("hot_reload_enable", false);
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
#if defined(__linux__)
|
|
static void SplitDirFile(const std::string& path, std::string& out_dir, std::string& out_file) {
|
|
auto pos = path.find_last_of('/');
|
|
if (pos == std::string::npos) {
|
|
out_dir = ".";
|
|
out_file = path;
|
|
return;
|
|
}
|
|
out_dir = path.substr(0, pos);
|
|
out_file = path.substr(pos + 1);
|
|
if (out_dir.empty()) out_dir = "/";
|
|
}
|
|
|
|
void InotifyWatchLoop(const std::string& path, std::atomic<bool>& stop_flag, GraphManager& mgr) {
|
|
std::string dir;
|
|
std::string file;
|
|
SplitDirFile(path, dir, file);
|
|
|
|
int fd = inotify_init1(IN_NONBLOCK);
|
|
if (fd < 0) {
|
|
return;
|
|
}
|
|
|
|
// Watch directory to catch atomic replace (write temp + rename) patterns.
|
|
int wd = inotify_add_watch(fd, dir.c_str(), IN_CLOSE_WRITE | IN_MOVED_TO | IN_CREATE | IN_MODIFY);
|
|
if (wd < 0) {
|
|
close(fd);
|
|
return;
|
|
}
|
|
|
|
std::vector<char> buf(4096);
|
|
while (!stop_flag.load()) {
|
|
int n = read(fd, buf.data(), static_cast<unsigned int>(buf.size()));
|
|
if (n <= 0) {
|
|
std::this_thread::sleep_for(std::chrono::milliseconds(200));
|
|
continue;
|
|
}
|
|
|
|
bool matched = false;
|
|
for (char* p = buf.data(); p < buf.data() + n;) {
|
|
auto* ev = reinterpret_cast<inotify_event*>(p);
|
|
if (ev->len > 0 && ev->name[0] != '\0') {
|
|
if (file == ev->name) {
|
|
matched = true;
|
|
break;
|
|
}
|
|
}
|
|
p += sizeof(inotify_event) + ev->len;
|
|
}
|
|
|
|
if (matched) {
|
|
std::string err;
|
|
if (!mgr.ReloadFromFile(path, err)) {
|
|
std::cerr << "[MediaServerApp] hot reload failed: " << err << "\n";
|
|
} else {
|
|
std::cout << "[MediaServerApp] hot reload succeeded\n";
|
|
}
|
|
// Debounce burst events.
|
|
std::this_thread::sleep_for(std::chrono::milliseconds(300));
|
|
}
|
|
}
|
|
|
|
inotify_rm_watch(fd, wd);
|
|
close(fd);
|
|
}
|
|
#endif
|
|
|
|
void PollWatchLoop(const std::string& path, std::atomic<bool>& stop_flag, GraphManager& mgr) {
|
|
#if __has_include(<filesystem>)
|
|
std::error_code ec;
|
|
auto last = fs::exists(path, ec) ? fs::last_write_time(path, ec) : fs::file_time_type{};
|
|
#endif
|
|
|
|
while (!stop_flag.load()) {
|
|
std::this_thread::sleep_for(std::chrono::seconds(1));
|
|
if (stop_flag.load()) break;
|
|
|
|
#if __has_include(<filesystem>)
|
|
std::error_code e2;
|
|
if (!fs::exists(path, e2)) continue;
|
|
auto cur = fs::last_write_time(path, e2);
|
|
if (e2) continue;
|
|
if (cur == last) continue;
|
|
last = cur;
|
|
|
|
std::string err;
|
|
if (!mgr.ReloadFromFile(path, err)) {
|
|
std::cerr << "[MediaServerApp] hot reload failed: " << err << "\n";
|
|
} else {
|
|
std::cout << "[MediaServerApp] hot reload succeeded\n";
|
|
}
|
|
#else
|
|
(void)path;
|
|
(void)mgr;
|
|
#endif
|
|
}
|
|
}
|
|
|
|
} // namespace
|
|
|
|
MediaServerApp::MediaServerApp(std::string config_path)
|
|
: config_path_(std::move(config_path)), graph_manager_(ResolvePluginDir()) {}
|
|
|
|
int MediaServerApp::Start() {
|
|
SimpleJson root_cfg;
|
|
std::string err;
|
|
if (!GraphManager::LoadConfigFile(config_path_, root_cfg, err)) {
|
|
std::cerr << "[MediaServerApp] Failed to load config: " << err << "\n";
|
|
return 1;
|
|
}
|
|
|
|
int metrics_port = 9000;
|
|
std::string web_root = "web";
|
|
if (const SimpleJson* g = root_cfg.Find("global")) {
|
|
metrics_port = g->ValueOr<int>("metrics_port", metrics_port);
|
|
web_root = g->ValueOr<std::string>("web_root", web_root);
|
|
}
|
|
|
|
if (!graph_manager_.Build(root_cfg, err)) {
|
|
std::cerr << "[MediaServerApp] Failed to build graphs: " << err << "\n";
|
|
return 1;
|
|
}
|
|
|
|
g_manager = &graph_manager_;
|
|
signal(SIGINT, HandleSignal);
|
|
signal(SIGTERM, HandleSignal);
|
|
|
|
if (!graph_manager_.StartAll()) {
|
|
std::cerr << "[MediaServerApp] Failed to start graphs\n";
|
|
return 1;
|
|
}
|
|
|
|
HttpServer http(graph_manager_, metrics_port, web_root);
|
|
(void)http.Start();
|
|
|
|
std::cout << "[MediaServerApp] Running. Press Ctrl+C to stop.\n";
|
|
|
|
std::atomic<bool> stop_watch{false};
|
|
std::thread watcher;
|
|
if (HotReloadEnabled(root_cfg)) {
|
|
#if defined(__linux__)
|
|
watcher = std::thread([&] {
|
|
// Prefer inotify; if it fails, fall back to polling.
|
|
InotifyWatchLoop(config_path_, stop_watch, graph_manager_);
|
|
PollWatchLoop(config_path_, stop_watch, graph_manager_);
|
|
});
|
|
#else
|
|
watcher = std::thread([&] { PollWatchLoop(config_path_, stop_watch, graph_manager_); });
|
|
#endif
|
|
std::cout << "[MediaServerApp] Hot reload enabled\n";
|
|
}
|
|
|
|
graph_manager_.BlockUntilStop();
|
|
|
|
http.Stop();
|
|
|
|
stop_watch.store(true);
|
|
if (watcher.joinable()) watcher.join();
|
|
std::cout << "[MediaServerApp] Shutdown complete.\n";
|
|
return 0;
|
|
}
|
|
|
|
} // namespace rk3588
|