OrangePi3588Media/src/media_server_app.cpp

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