diff --git a/man/mu-server.1.org b/man/mu-server.1.org index cd401e0f..3d90b8f4 100644 --- a/man/mu-server.1.org +++ b/man/mu-server.1.org @@ -61,10 +61,16 @@ directly through the emacs process input/output. This is noticeably faster for commands with a lot of output, esp. when the the temp-file uses a in-memory file-system. +** --listen +If set, the server starts an SCM REPL as well, which listens on a Unix domain +socket. This corresponds to the *--listen* options for *mu scm**. + +The store object (including the Xapian database) is shared between the server and the REPL. + * PERFORMANCE As an indication for the relative performance, we can simulate something ~mu4e~ -does; we take overall time of 50 such requests: +does. We take the overall time of 50 such requests: #+begin_src sh time build/mu/mu server --allow-temp-file --eval '(find :query "\"\"" :include-related t :threads t :maxnum 50000)' >/dev/null @@ -78,7 +84,6 @@ time build/mu/mu server --allow-temp-file --eval '(find :query "\"\"" :include-r | 1.10 | 5.7s | | 1.11 (master) | 2.8s | - #+include: "muhome.inc" :minlevel 2 #+include: "common-options.inc" :minlevel 1 @@ -87,4 +92,5 @@ time build/mu/mu server --allow-temp-file --eval '(find :query "\"\"" :include-r * SEE ALSO -{{{man-link(mu,1)}}} +{{{man-link(mu,1)}}}, +{{{man-link(mu-scm,1)}}} diff --git a/mu/mu-cmd-server.cc b/mu/mu-cmd-server.cc index 3a694566..280ec4e3 100644 --- a/mu/mu-cmd-server.cc +++ b/mu/mu-cmd-server.cc @@ -1,5 +1,5 @@ /* -** Copyright (C) 2020-2023 Dirk-Jan C. Binnema +** Copyright (C) 2020-2025 Dirk-Jan C. Binnema ** ** This program is free software; you can redistribute it and/or modify it ** under the terms of the GNU General Public License as published by the @@ -29,6 +29,10 @@ #include "mu-cmd.hh" #include "mu-server.hh" +#if BUILD_SCM +#include "scm/mu-scm.hh" +#endif/*BUILD_SCM*/ + #include "utils/mu-utils.hh" #include "utils/mu-readline.hh" @@ -60,6 +64,8 @@ install_sig_handler() } /* + * djcb@djcbsoftware.nl + * * Markers for/after the length cookie that precedes the expression we write to * output. We use octal 376, 377 (ie, 0xfe, 0xff) as they will never occur in * utf8 @@ -102,6 +108,46 @@ report_error(const Mu::Error& err) noexcept Server::OutputFlags::Flush); } +static void +maybe_setup_readline(const Mu::Options& opts) +{ + tty = ::isatty(::fileno(stdout)); + + // Note, the readline stuff is inactive unless on a tty. + const auto histpath{opts.runtime_path(RuntimePath::Cache) + "/history"}; + setup_readline(histpath, 50); +} + +// get the SCM socket path we're listening on, or empty if not listening +static std::string +maybe_listen_path(const Mu::Store& store, const Mu::Options& opts) +{ +#ifdef BUILD_SCM + if (!opts.scm.socket_path) + return {}; + const auto res = Mu::Scm::run(store, opts, false/*!block*/); + if (!res) { + mu_warning("failed to start scm socket: {}", res.error().what()); + return {}; + } else + return *opts.scm.socket_path; +#endif /*BUILD_SCM*/ + return {}; +} + +static bool +maybe_eval(Server& server, const Mu::Options& opts) +{ + const auto eval = std::string{opts.server.commands ? + "(help :full t)" : opts.server.eval}; + + if (!eval.empty()) { + server.invoke(eval); + return true; + } else + return false; +} + Result Mu::mu_cmd_server(const Mu::Options& opts) try { @@ -110,31 +156,23 @@ Mu::mu_cmd_server(const Mu::Options& opts) try { if (!store) return Err(store.error()); - Server::Options sopts{}; - sopts.allow_temp_file = opts.server.allow_temp_file; + // empty when we're not listening + const auto socket_path = maybe_listen_path(*store, opts); + Server::Options sopts{opts.server.allow_temp_file, socket_path}; Server server{*store, sopts, output_stdout}; - mu_message("created server with store @ {}; maildir @ {}; debug-mode {};" - "readline: {}", - store->path(), store->root_maildir(), - opts.debug ? "yes" : "no", - have_readline() ? "yes" : "no"); - tty = ::isatty(::fileno(stdout)); - const auto eval = std::string{opts.server.commands ? "(help :full t)" : opts.server.eval}; - if (!eval.empty()) { - server.invoke(eval); + if (maybe_eval(server, opts)) return Ok(); - } - - // Note, the readline stuff is inactive unless on a tty. - const auto histpath{opts.runtime_path(RuntimePath::Cache) + "/history"}; - setup_readline(histpath, 50); + maybe_setup_readline(opts); install_sig_handler(); - mu_println(";; Welcome to the " PACKAGE_STRING " command-server{}\n" - ";; Use (help) to get a list of commands, (quit) to quit.", + + mu_println(";; Welcome to the " PACKAGE_STRING " command-server{}", opts.debug ? " (debug-mode)" : ""); + if (!socket_path.empty()) + mu_println(";; SCM socket listening on {}", socket_path); + mu_println(";; Use (help) to get a list of commands, (quit) to quit."); bool do_quit{}; while (!MuTerminate && !do_quit) { @@ -151,7 +189,6 @@ Mu::mu_cmd_server(const Mu::Options& opts) try { mu_message ("shutting down due to signal {}", MuTerminate.load()); shutdown_readline(); - return Ok(); } catch (const Error& er) { /* note: user-level error, "OK" for mu */ diff --git a/mu/mu-options.cc b/mu/mu-options.cc index b91138eb..0db3bb65 100644 --- a/mu/mu-options.cc +++ b/mu/mu-options.cc @@ -616,6 +616,24 @@ sub_remove(CLI::App& sub, Options& opts) ->type_name(""); } +static std::string +concoct_socket_path() +{ + return join_paths(g_get_user_runtime_dir(), + mu_format("mu-scm-{}.sock", ::getpid())); +} + +static void +add_listen_flag(CLI::App& sub, Options& opts) +{ + sub.add_flag("--listen", opts.scm.listen, + "Start SCM REPL on a domain socket"); + sub.callback([&opts]{ + if (opts.scm.listen) + opts.scm.socket_path = concoct_socket_path(); + }); +} + static void sub_server(CLI::App& sub, Options& opts) { @@ -627,18 +645,23 @@ sub_server(CLI::App& sub, Options& opts) sub.add_flag("--allow-temp-file", opts.server.allow_temp_file, "Allow for the temp-file optimization") ->excludes("--commands"); + +#if BUILD_SCM + add_listen_flag(sub, opts); +#endif/*BUILD_SCM*/ } static void sub_scm(CLI::App& sub, Options& opts) { - sub.add_flag("--listen", opts.scm.listen, - "Start listening on a domain socket"); + add_listen_flag(sub, opts); + sub.add_option("script-path", opts.scm.script_path, "Path to script") ->type_name("") ->excludes("--listen"); sub.add_option("script-args", opts.scm.params, "Parameters for script") ->type_name(""); + } static void @@ -787,7 +810,6 @@ AssocPairs SubCommandInfos= {{ }}; - static ScriptInfos add_scripts(CLI::App& app, Options& opts) { diff --git a/mu/mu-options.hh b/mu/mu-options.hh index 245678dc..e0a51fe8 100644 --- a/mu/mu-options.hh +++ b/mu/mu-options.hh @@ -46,13 +46,13 @@ struct Options { /* * general options */ - bool quiet; /**< don't give any output */ - bool debug; /**< log debug-level info */ - bool version; /**< request mu version */ - bool log_stderr; /**< log to stderr */ - bool nocolor; /**< don't use use ansi-colors */ - bool verbose; /**< verbose output */ - std::string muhome; /**< alternative mu dir */ + bool quiet; /**< don't give any output */ + bool debug; /**< log debug-level info */ + bool version; /**< request mu version */ + bool log_stderr; /**< log to stderr */ + bool nocolor; /**< don't use use ansi-colors */ + bool verbose; /**< verbose output */ + std::string muhome; /**< alternative mu dir */ /** * Whether by default, we should show color @@ -260,6 +260,8 @@ struct Options { OptString script_path; /**< Path to script (optional) */ StringVec params; /**< Parameters for script (after "--") */ bool listen; /**< Whether to start listening on a socket */ + Option socket_path; /**< path for the '--listen' + * Unix domain socket */ } scm; diff --git a/mu/mu.cc b/mu/mu.cc index 69d3c485..0a580bc4 100644 --- a/mu/mu.cc +++ b/mu/mu.cc @@ -77,6 +77,10 @@ handle_result(const Result& res, const Mu::Options& opts) return res.error().exit_code(); } +namespace { +Options opts; // it's big, don't want it on the stack +} + int main(int argc, char* argv[]) try { @@ -93,34 +97,36 @@ main(int argc, char* argv[]) try /* * read command-line options */ - const auto opts{Options::make(argc, argv)}; - if (!opts) { - output_error(opts.error().what(), !Options::default_no_color()); - return opts.error().exit_code(); - } else if (!opts->sub_command) { + auto optsres{Options::make(argc, argv)}; + if (!optsres) { + output_error(optsres.error().what(), !Options::default_no_color()); + return optsres.error().exit_code(); + } else if (!optsres->sub_command) { // nothing more to do. return 0; } + opts = std::move(*optsres); + // setup logging Logger::Options lopts{Logger::Options::None}; - if (opts->log_stderr) + if (opts.log_stderr) lopts |= Logger::Options::StdOutErr; - if (opts->debug) + if (opts.debug) lopts |= Logger::Options::Debug; if (!!g_getenv("MU_TEST")) lopts |= Logger::Options::File; - const auto logger{Logger::make(opts->runtime_path(RuntimePath::LogFile), lopts)}; + const auto logger{Logger::make(opts.runtime_path(RuntimePath::LogFile), lopts)}; if (!logger) { - output_error(logger.error().what(), !opts->nocolor); + output_error(logger.error().what(), !opts.nocolor); return logger.error().exit_code(); } /* * handle sub command */ - return handle_result(mu_cmd_execute(*opts), *opts); + return handle_result(mu_cmd_execute(opts), opts); // exceptions should have been handled earlier, but catch them here, // just in case...