diff options
Diffstat (limited to 'tools/inspect/inspect.vala')
-rw-r--r-- | tools/inspect/inspect.vala | 620 |
1 files changed, 0 insertions, 620 deletions
diff --git a/tools/inspect/inspect.vala b/tools/inspect/inspect.vala deleted file mode 100644 index c4860d76..00000000 --- a/tools/inspect/inspect.vala +++ /dev/null @@ -1,620 +0,0 @@ -/* - * Copyright (C) 2010 Collabora Ltd. - * Copyright (C) 2012 Philip Withnall - * - * This library is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as published by - * the Free Software Foundation, either version 2.1 of the License, or - * (at your option) any later version. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this library. If not, see <http://www.gnu.org/licenses/>. - * - * Authors: - * Philip Withnall <philip.withnall@collabora.co.uk> - */ - -using Folks.Inspect.Commands; -using Folks; -using Readline; -using Gee; -using GLib; -using Posix; - -/* We have to have a static global instance so that the readline callbacks can - * access its data, since they don't pass closures around. */ -static Inspect.Client main_client = null; - -public class Folks.Inspect.Client : Object -{ - public HashMap<string, Command> commands; - private static bool _is_readline_installed; - private MainLoop main_loop; - public IndividualAggregator aggregator { get; private set; } - public BackendStore backend_store { get; private set; } - public SignalManager signal_manager { get; private set; } - - /* To page or not to page? */ - private termios _original_termios_p; - private bool _original_termios_p_valid = false; - private bool _quit_after_pager_dies = false; - private static Pid _pager_pid = 0; - private IOChannel? _stdin_channel = null; - private static uint _stdin_watch_id = 0; - private FileStream? _pager_channel = null; - private uint _pager_child_watch_id = 0; - - public static int main (string[] args) - { - int retval = 0; - - Intl.setlocale (LocaleCategory.ALL, ""); - Intl.bindtextdomain (BuildConf.GETTEXT_PACKAGE, BuildConf.LOCALE_DIR); - Intl.textdomain (BuildConf.GETTEXT_PACKAGE); - - /* Parse command line options. */ - OptionContext context = new OptionContext ("[COMMAND]"); - context.set_summary ("Inspect meta-contact information in libfolks."); - - try - { - context.parse (ref args); - } - catch (OptionError e1) - { - GLib.stderr.printf ("Couldn’t parse command line options: %s\n", - e1.message); - return 1; - } - - /* Create the client. */ - main_client = new Client (); - - /* Set up signal handling. */ -#if VALA_0_40 - Unix.signal_add (Posix.Signal.TERM, () => -#else - Unix.signal_add (Posix.SIGTERM, () => -#endif - { - /* Propagate the signal to our pager process, if it's running. */ - if (Client._pager_pid != 0) - { - main_client._quit_after_pager_dies = true; -#if VALA_0_40 - kill (Client._pager_pid, Posix.Signal.TERM); -#else - kill (Client._pager_pid, Posix.SIGTERM); -#endif - } - else - { - /* Quit the client and let that exit the process. */ - main_client.quit (); - } - - return false; - }); - - /* Run the command. */ - if (args.length == 1) - { - main_client.run_interactive.begin (); - retval = 0; - } - else - { - GLib.assert (args.length > 1); - - /* Drop the first argument and parse the rest as a command line. If - * the first argument is ‘--’ then the command was passed after some - * flags. */ - string command_line; - if (args[1] == "--") - { - command_line = string.joinv (" ", args[2:0]); - } - else - { - command_line = string.joinv (" ", args[1:0]); - } - - main_client.run_non_interactive.begin (command_line, (obj, res) => - { - retval = main_client.run_non_interactive.end (res); - main_client.quit (); - }); - } - - main_client.main_loop.run (); - - return retval; - } - - public Client () - { - Utils.init (); - - this.commands = new HashMap<string, Command> (); - - /* Register the commands we support */ - /* FIXME: This should be automatic */ - this.commands.set ("quit", new Commands.Quit (this)); - this.commands.set ("help", new Commands.Help (this)); - this.commands.set ("individuals", new Commands.Individuals (this)); - this.commands.set ("linking", new Commands.Linking (this)); - this.commands.set ("personas", new Commands.Personas (this)); - this.commands.set ("backends", new Commands.Backends (this)); - this.commands.set ("persona-stores", new Commands.PersonaStores (this)); - this.commands.set ("set", new Commands.Set (this)); - this.commands.set ("signals", new Commands.Signals (this)); - this.commands.set ("debug", new Commands.Debug (this)); - this.commands.set ("search", new Commands.Search (this)); - - /* Create various bits of folks machinery. */ - this.main_loop = new MainLoop (); - this.signal_manager = new SignalManager (); - this.backend_store = BackendStore.dup (); - this.aggregator = IndividualAggregator.dup (); - } - - public void quit () - { - /* Stop paging. */ - this._stop_paged_output (); - - /* Uninstall readline, if it's installed. */ - if (Client._is_readline_installed) - { - this._uninstall_readline_and_stdin (); - } - - /* Restore the user's original terminal settings, since the pager might've - * fiddled with them. */ - if (this._original_termios_p_valid) - { - tcsetattr (Posix.STDIN_FILENO, Posix.TCSADRAIN, - this._original_termios_p); - } - - /* Kill the main loop. */ - this.main_loop.quit (); - } - - private async void _wait_for_quiescence () throws GLib.Error - { - var has_yielded = false; - var signal_id = this.aggregator.notify["is-quiescent"].connect ( - (obj, pspec) => - { - if (has_yielded == true) - { - this._wait_for_quiescence.callback (); - } - }); - - try - { - yield this.aggregator.prepare (); - - if (this.aggregator.is_quiescent == false) - { - has_yielded = true; - yield; - } - } - finally - { - this.aggregator.disconnect (signal_id); - GLib.assert (this.aggregator.is_quiescent == true); - } - } - - public async int run_non_interactive (string command_line) - { - /* Non-interactive mode: run a single command and output the results. - * We do this all from the main thread, in a main loop, waiting for - * quiescence before running the command. */ - - /* Check we can parse the command first. */ - string subcommand; - string command_name; - var command = Client.parse_command_line (command_line, out command_name, - out subcommand); - - if (command == null) - { - GLib.stdout.printf ("Unrecognised command ‘%s’.\n", command_name); - return 1; - } - - /* Wait until we reach quiescence, or the results will probably be - * useless. */ - try - { - yield this._wait_for_quiescence (); - } - catch (GLib.Error e1) - { - GLib.stderr.printf ("Error preparing aggregator: %s\n", e1.message); - return 1; - } - - /* Run the command */ - int retval = yield command.run (subcommand); - this.quit (); - - return retval; - } - - public async int run_interactive () - { - /* Interactive mode: have a little shell which allows the data from - * libfolks to be browsed and edited in real time. We do this by watching - * stdin in our main loop, and passing character notifications to - * readline. The main loop also processes all the folks events, thus - * preventing us having to run a second thread. */ - - /* Copy the user's original terminal settings. */ - if (tcgetattr (Posix.STDIN_FILENO, out this._original_termios_p) == 0) - { - this._original_termios_p_valid = true; - } - - /* Handle SIGINT. */ -#if VALA_0_40 - Unix.signal_add (Posix.Signal.INT, () => -#else - Unix.signal_add (Posix.SIGINT, () => -#endif - { - if (Client._is_readline_installed == false) - { - return true; - } - - /* Tidy up. */ - Readline.free_line_state (); - Readline.cleanup_after_signal (); - Readline.reset_after_signal (); - - /* Display a fresh prompt. */ - GLib.stdout.printf ("^C"); - Readline.crlf (); - Readline.reset_line_state (); - Readline.replace_line ("", 0); - Readline.redisplay (); - - return true; - }); - - /* Allow things to be set for folks-inspect in ~/.inputrc, and install our - * own completion function. */ - Readline.readline_name = "folks-inspect"; - Readline.attempted_completion_function = Client.completion_cb; - Readline.catch_signals = 0; /* go away, readline */ - - /* Install readline and the stdin handler. */ - this._stdin_channel = new IOChannel.unix_new (GLib.stdin.fileno ()); - this._install_readline_and_stdin (); - - /* Run the aggregator and the main loop. */ - this.aggregator.prepare.begin (); - - return 0; - } - - private void _install_readline_and_stdin () - { - /* stdin handler. */ - Client._stdin_watch_id = this._stdin_channel.add_watch (IOCondition.IN, - this._stdin_handler_cb); - - /* Callback for each character appearing on stdin. */ - Readline.callback_handler_install ("> ", Client._readline_handler_cb); - Client._is_readline_installed = true; - } - - private void _uninstall_readline_and_stdin () - { - Readline.callback_handler_remove (); - Client._is_readline_installed = false; - - Source.remove (Client._stdin_watch_id); - Client._stdin_watch_id = 0; - } - - /* This should only ever be called while readline is installed. */ - private bool _stdin_handler_cb (IOChannel source, IOCondition cond) - { - /* At least a single character is available on stdin, so let readline - * consume it. */ - if ((cond & IOCondition.IN) != 0) - { - Readline.callback_read_char (); - return true; - } - - assert_not_reached (); - } - - private static void _readline_handler_cb (string? _command_line) - { - if (_command_line == null) - { - /* EOF. If we've entered some text, don't do anything. Otherwise, - * quit. */ - if (Readline.line_buffer != "") - { - Readline.ding (); - return; - } - - /* Quit. */ - main_client.quit (); - - return; - } - - var command_line = (!) _command_line; - - command_line = command_line.strip (); - if (command_line == "") - { - /* If the user's entered a blank line, just display a new prompt - * without doing anything else. */ - return; - } - - string subcommand; - string command_name; - Command command = Client.parse_command_line (command_line, - out command_name, out subcommand); - - /* Run the command */ - if (command != null) - { - if (command_name != "quit") - { - /* Start paging output. This is stopped when the pager dies. */ - main_client._start_paged_output (); - } - - command.run.begin (subcommand, (obj, res) => - { - command.run.end (res); - - if (main_client._pager_channel != null) - { - /* Close the stream to the pager so it knows it's - * reached EOF. */ - main_client._pager_channel = null; - Utils.output_filestream = GLib.stdout; - } - else - { - /* Failed to start the pager in the first place. */ - Readline.reset_line_state (); - Readline.replace_line ("", 0); - Readline.redisplay (); - } - }); - - } - else - { - GLib.stdout.printf ("Unrecognised command ‘%s’.\n", command_name); - } - - /* Store the command in the history, even if it failed */ - Readline.History.add (command_line); - } - - private void _start_paged_output () - { - /* If the output is not a TTY (because it's a pipe or a file or a - * toaster) we don't page. */ - if (!isatty (1)) - { - return; - } - - var pager = Environment.get_variable ("PAGER"); - if (pager != null && pager == "") - { - return; - } - - if (pager == null) - { - pager = "less -FRSX"; - } - - /* Convert command to null terminated array */ - string[] args; - try - { - GLib.Shell.parse_argv (pager, out args); - } - catch (GLib.ShellError e) - { - warning ("Error parsing pager arguments: %s", e.message); - return; - } - - /* Remove the readline and stdin handlers while the pager is running. */ - this._uninstall_readline_and_stdin (); - - /* Store the readline terminal state so that we can restore them - * after the pager has exited. */ - Readline.prep_terminal (1); - - /* Spawn the pager. */ - int pager_fd = 0; - - try - { - GLib.Process.spawn_async_with_pipes (null, - args, - null, - GLib.SpawnFlags.LEAVE_DESCRIPTORS_OPEN | - GLib.SpawnFlags.SEARCH_PATH | - GLib.SpawnFlags.DO_NOT_REAP_CHILD /* we use a ChildWatch */, - null, - out Client._pager_pid, - out pager_fd, // Std input - null, // Std out - null); // Std error - } - catch (SpawnError e2) - { - warning ("Error spawning pager: %s", e2.message); - - /* Reinstall the readline handler and stdin handler. */ - this._install_readline_and_stdin (); - - return; - } - - /* Redirect our output to the pager. */ - this._pager_channel = FileStream.fdopen (pager_fd, "w"); - Utils.output_filestream = this._pager_channel; - - /* Watch for when the pager exits. */ - this._pager_child_watch_id = ChildWatch.add (Client._pager_pid, - (pid, status) => - { - /* $PAGER died or was killed. */ - this._stop_paged_output (); - - /* Reset the readline state ready to display a new prompt. If the - * pager exited as the result of a signal, it probably didn't - * tidy up after itself (e.g. ``less`` leaves a colon prompt - * behind on the current line), so move to a new line. Doing this - * normally just looks a bit weird. */ - if (Process.if_signaled (status)) - { - Readline.crlf (); - } - - Readline.reset_line_state (); - Readline.replace_line ("", 0); - - /* Are we supposed to quit (e.g. due to receiving a SIGTERM)? */ - if (this._quit_after_pager_dies) - { - main_client.quit (); - return; - } - - /* Reinstall the readline handler and stdin handler. */ - this._install_readline_and_stdin (); - }); - } - - private void _stop_paged_output () - { - if (Client._pager_pid == 0) - { - return; - } - - Process.close_pid (Client._pager_pid); - Source.remove (this._pager_child_watch_id); - - this._pager_channel = null; - Utils.output_filestream = GLib.stdout; - Client._pager_pid = 0; - this._pager_child_watch_id = 0; - - /* Reset the terminal state (e.g. ECHO, which can get left turned - * off if the pager was killed uncleanly). */ - Readline.deprep_terminal (); - Readline.free_line_state (); - Readline.cleanup_after_signal (); - Readline.reset_after_signal (); - } - - private static Command? parse_command_line (string command_line, - out string command_name, - out string? subcommand) - { - /* Default output */ - command_name = ""; - subcommand = null; - - string[] parts = command_line.split (" ", 2); - - if (parts.length < 1) - return null; - - command_name = parts[0]; - if (parts.length == 2 && parts[1] != "") - subcommand = parts[1]; - else - subcommand = null; - - /* Extract the first part of the command and see if it matches anything in - * this.commands */ - return main_client.commands.get (parts[0]); - } - - [CCode (array_length = false, array_null_terminated = true)] - private static string[]? completion_cb (string word, - int start, - int end) - { - /* word is the word to complete, and start and end are its bounds inside - * Readline.line_buffer, which contains the entire current line. */ - - /* Command name completion */ - if (start == 0) - { - return Readline.completion_matches (word, - Utils.command_name_completion_cb); - } - - /* Command parameter completion is passed off to the Command objects */ - string command_name; - string subcommand; - Command command = Client.parse_command_line (Readline.line_buffer, - out command_name, - out subcommand); - - if (command != null) - { - if (subcommand == null) - subcommand = ""; - return command.complete_subcommand (subcommand); - } - - return null; - } -} - -public abstract class Folks.Inspect.Command -{ - protected Client client; - - protected Command (Client client) - { - this.client = client; - } - - public abstract string name { get; } - public abstract string description { get; } - public abstract string help { get; } - - public abstract async int run (string? command_string); - - public virtual string[]? complete_subcommand (string subcommand) - { - /* Default implementation */ - return null; - } -} |