#include #include // for malloc_trim(3) import std; import routemon; namespace chrono = std::chrono; namespace net = boost::asio; enum class exit_status { failure, bad_usage, }; auto real_main(std::span args) -> exit_status { auto sink = routemon::log::make_sink(routemon::log::level::info); auto l = routemon::log::logger{sink}; if (args.size() != 2) { l.error( "Fatal: expected exactly one argument (the configuration file " "location), got {}", args.size() - 1); return exit_status::bad_usage; } auto const* config_filename = args[1]; auto lgen = routemon::locale::make_generator(); auto default_locale = lgen->generate("en_US.UTF-8"); auto locales = { default_locale, lgen->generate("nl_NL.UTF-8"), lgen->generate("de_DE.UTF-8"), lgen->generate("en_GB.UTF-8"), }; auto lsel = routemon::locale::selector{locales, default_locale, lgen}; auto ioc = net::io_context{1 /* concurrency hint */}; auto config = routemon::config::app{}; try { config = routemon::config::load_file(config_filename); } catch (std::exception const& e) { l.with("filename", std::string_view{config_filename}) .error("Failed to load configuration: {}", e.what()); return exit_status::failure; } sink->set_level(config.logger.level); // auto rwgps_client = routemon::rwgps::client{ioc, l, config.rwgps.api_key, // config.rwgps.auth_token}; for (auto route : // rwgps_client.get_all_routes()) // { // l.info("Route {} (user {}): {} @ {}", route.id, route.user_id, // route.name, route.url); // } auto dbc = std::shared_ptr{}; try { dbc = routemon::database::open(config.database.sqlite3_filename); } catch (std::exception const& e) { l.with("filename", config.database.sqlite3_filename) .error("Failed to open database: {}", e.what()); return exit_status::failure; } l.with("filename", config.situations.datex2_filename) .info("Loading situations"); auto const before_load = chrono::steady_clock::now(); auto d2loader = routemon::datex2::loader{}; auto pub = routemon::datex2::situation_publication{}; try { pub = d2loader.load_situation_publication(config.situations.datex2_filename); } catch (std::exception const& e) { l.error("Failed to load DATEX II situations publication: {}", e.what()); return exit_status::failure; } if (!d2loader.warnings().empty()) { auto const& warns = d2loader.warnings(); l.warn( "Encountered {} unique warnings while loading DATEX II situations " "publication", warns.size()); auto i = 0uz; for (auto it = warns.begin(); it != warns.end(); it = warns.upper_bound(*it)) { l.warn("Warning {} (appeared {}×): {}", ++i, warns.count(*it), *it); } } // Processing the feed is by far the most memory-intensive operation // during the run time of this application (at the moment), the // resident set will likely never be this big again. So we ask libc // to return as much memory as possible to the OS. malloc_trim(0); auto const after_load = chrono::steady_clock::now(); auto const dur_load = chrono::duration_cast(after_load - before_load); l.info("Loading situations finished in {}", dur_load); auto handler = routemon::api::handler{l, std::move(pub)}; auto http_server = routemon::srv::server{l, std::move(lsel), std::move(handler)}; http_server.spawn(ioc); ioc.run(); l.error("I/O context stopped"); return exit_status::failure; } auto main(int argc, char* argv[]) -> int { if (argc < 0) { std::cout << "Fatal: argument count below zero" << std::endl; return EXIT_FAILURE; } auto res = real_main( std::span{ const_cast(argv), static_cast(argc) }); switch (res) { case exit_status::failure: return EXIT_FAILURE; case exit_status::bad_usage: return 2; } }