#include "media_server_app.h" #include #include #include #include #include #include #include #include #if __has_include() #include namespace fs = std::filesystem; #endif #if defined(__linux__) #include #include #include #include #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(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("enable", false); } // Backward compatible: allow global.hot_reload_enable if (g->Find("hot_reload_enable")) { return g->ValueOr("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& 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 buf(4096); while (!stop_flag.load()) { int n = read(fd, buf.data(), static_cast(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(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& stop_flag, GraphManager& mgr) { #if __has_include() 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() 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("metrics_port", metrics_port); web_root = g->ValueOr("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 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