From: Philip Withnall Date: Mon, 6 Sep 2010 18:38:21 +0000 (+0100) Subject: Bug 629075 — Add folks command line application X-Git-Tag: FOLKS_0_3_4~52 X-Git-Url: http://review.tizen.org/git/?a=commitdiff_plain;h=96b1a7b28989b50cc0bb2241ece5889a2b7fb0cf;p=platform%2Fupstream%2Ffolks.git Bug 629075 — Add folks command line application Add an interactive command line inspection utility for libfolks. Closes: bgo#629075. --- diff --git a/NEWS b/NEWS index 3cd95df..6f03ea9 100644 --- a/NEWS +++ b/NEWS @@ -1,11 +1,15 @@ Overview of changes from libfolks 0.3.3 to libfolks 0.3.4 ========================================================= +Major changes: +* Add folks-inspect tool + Bugs fixed: * Bug 637240 — libfolks-telepathy.so exports private symbols * Bug 638311 — Add a HACKING file that outlines development policies and coding style * Bug 629083 — Review coding conventions in folks +* Bug 629075 — Add folks command line application Overview of changes from libfolks 0.3.2 to libfolks 0.3.3 ========================================================= diff --git a/configure.ac b/configure.ac index 4c2ace4..662980d 100644 --- a/configure.ac +++ b/configure.ac @@ -193,6 +193,25 @@ AS_IF([test "$enable_import_tool" = "yes" -a "$with_import_tool" = "no"], [AC_MSG_ERROR([Import tool explicitly enabled, but libxml2 not found])]) AM_CONDITIONAL([ENABLE_IMPORT_TOOL], [test "$with_import_tool" = "yes"]) +# Readline's required for the folks-inspect program +AC_ARG_ENABLE([inspect-tool], + AS_HELP_STRING([--enable-inspect-tool], + [Enable building the data inspection tool]), + [enable_inspect_tool=$enableval with_inspect_tool=$enableval], + [enable_inspect_tool=maybe with_inspect_tool=no]) +AS_IF([test "$enable_inspect_tool" != "no"], + [AC_CHECK_LIB([readline], [main], + [with_inspect_tool=yes], [with_inspect_tool=no], + [-lncurses])]) +AS_IF([test "$enable_inspect_tool" != "no" -a "$with_inspect_tool" = "yes"], + [VALA_CHECK_PACKAGES([readline]) + LIBREADLINE="-lreadline -lncurses" + AC_DEFINE([HAVE_LIBREADLINE], [1], [Define if you have libreadline]) + AC_SUBST([LIBREADLINE])]) +AS_IF([test "$enable_inspect_tool" = "yes" -a "$with_inspect_tool" = "no"], + [AC_MSG_ERROR([Inspect tool explicitly enabled, but readline not found])]) +AM_CONDITIONAL([ENABLE_INSPECT_TOOL], [test "$with_inspect_tool" = "yes"]) + # ----------------------------------------------------------- # Documentation # ----------------------------------------------------------- @@ -304,6 +323,7 @@ AC_CONFIG_FILES([ tests/lib/telepathy/contactlist/session.conf tests/tools/Makefile tools/Makefile + tools/inspect/Makefile ]) AC_OUTPUT @@ -319,6 +339,7 @@ Configure summary: Documentation...............: ${HAVE_VALADOC} Tests.......................: ${have_tp_glib_for_tests} Import tool.................: ${with_import_tool} + Inspector tool..............: ${with_inspect_tool} " diff --git a/tools/Makefile.am b/tools/Makefile.am index 17ab8e7..9bcce9c 100644 --- a/tools/Makefile.am +++ b/tools/Makefile.am @@ -1,3 +1,11 @@ +# Inspector +DIST_SUBDIRS = inspect + +if ENABLE_INSPECT_TOOL +SUBDIRS = inspect +endif + +# Importer if ENABLE_IMPORT_TOOL bin_PROGRAMS = folks-import endif diff --git a/tools/inspect/Makefile.am b/tools/inspect/Makefile.am new file mode 100644 index 0000000..715574a --- /dev/null +++ b/tools/inspect/Makefile.am @@ -0,0 +1,40 @@ +VALAFLAGS = \ + --vapidir=$(top_srcdir)/folks \ + --pkg=readline \ + --pkg=gobject-2.0 \ + --pkg=gee-1.0 \ + --pkg=folks \ + $(NULL) + +bin_PROGRAMS = folks-inspect + +folks_inspect_SOURCES = \ + command-backends.vala \ + command-help.vala \ + command-individuals.vala \ + command-persona-stores.vala \ + command-personas.vala \ + command-quit.vala \ + command-signals.vala \ + signal-manager.vala \ + utils.vala \ + inspect.vala \ + $(NULL) +folks_inspect_LDADD = \ + $(LIBREADLINE) \ + $(GLIB_LIBS) \ + $(GEE_LIBS) \ + $(top_builddir)/folks/libfolks.la \ + $(NULL) +folks_inspect_CFLAGS = \ + -I$(top_srcdir)/folks \ + $(GLIB_CFLAGS) \ + $(GEE_CFLAGS) \ + $(NULL) + +GITIGNOREFILES = \ + folks_inspect_vala.stamp \ + $(folks_inspect_SOURCES:.vala=.c) \ + $(NULL) + +-include $(top_srcdir)/git.mk diff --git a/tools/inspect/command-backends.vala b/tools/inspect/command-backends.vala new file mode 100644 index 0000000..d3a5d18 --- /dev/null +++ b/tools/inspect/command-backends.vala @@ -0,0 +1,102 @@ +/* + * Copyright (C) 2010 Collabora Ltd. + * + * 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 . + * + * Authors: + * Philip Withnall + */ + +using Folks; +using Gee; +using GLib; + +private class Folks.Inspect.Commands.Backends : Folks.Inspect.Command +{ + public override string name + { + get { return "backends"; } + } + + public override string description + { + get { return "Inspect the backends loaded by the aggregator."; } + } + + public override string help + { + get + { + return "backends List all known backends.\n" + + "backends [backend name] Display the details of the " + + "specified backend and list its persona stores."; + } + } + + public Backends (Client client) + { + base (client); + } + + public override void run (string? command_string) + { + if (command_string == null) + { + /* List all the backends */ + Collection backends = + this.client.backend_store.list_backends (); + + Utils.print_line ("%u backends:", backends.size); + + Utils.indent (); + foreach (Backend backend in backends) + Utils.print_line ("%s", backend.name); + Utils.unindent (); + } + else + { + /* Show the details of a particular backend */ + Backend backend = + this.client.backend_store.get_backend_by_name (command_string); + + if (backend == null) + { + Utils.print_line ("Unrecognised backend name '%s'.", + command_string); + return; + } + + Utils.print_line ("Backend '%s' with %u persona stores " + + "(type ID, ID ('display name')):", + backend.name, backend.persona_stores.size ()); + + /* List the backend's persona stores */ + Utils.indent (); + backend.persona_stores.foreach ((k, v) => + { + PersonaStore store = (PersonaStore) v; + Utils.print_line ("%s, %s ('%s')", store.type_id, store.id, + store.display_name); + }); + Utils.unindent (); + } + } + + public override string[]? complete_subcommand (string subcommand) + { + /* @subcommand should be a backend name */ + return Readline.completion_matches (subcommand, + Utils.backend_name_completion_cb); + } +} diff --git a/tools/inspect/command-help.vala b/tools/inspect/command-help.vala new file mode 100644 index 0000000..21d3b03 --- /dev/null +++ b/tools/inspect/command-help.vala @@ -0,0 +1,89 @@ +/* + * Copyright (C) 2010 Collabora Ltd. + * + * 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 . + * + * Authors: + * Philip Withnall + */ + +using Folks; +using Gee; +using GLib; + +private class Folks.Inspect.Commands.Help : Folks.Inspect.Command +{ + public override string name + { + get { return "help"; } + } + + public override string description + { + get { return "Get help on using the program."; } + } + + public override string help + { + get + { + return "help Describe all the available " + + "commands.\n" + + "help [command name] Give more detailed help on the " + + "specified command."; + } + } + + public Help (Client client) + { + base (client); + } + + public override void run (string? command_string) + { + if (command_string == null) + { + /* Help index */ + Utils.print_line ("Type 'help ' for more information " + + "about a particular command."); + + MapIterator iter = + this.client.commands.map_iterator (); + + Utils.indent (); + while (iter.next () == true) + { + Utils.print_line ("%-20s %s", iter.get_key (), + iter.get_value ().description); + } + Utils.unindent (); + } + else + { + /* Help for a given command */ + Command command = this.client.commands.get (command_string); + if (command == null) + Utils.print_line ("Unrecognised command '%s'.", command_string); + else + Utils.print_line ("%s", command.help); + } + } + + public override string[]? complete_subcommand (string subcommand) + { + /* @subcommand should be a command name */ + return Readline.completion_matches (subcommand, + Utils.command_name_completion_cb); + } +} diff --git a/tools/inspect/command-individuals.vala b/tools/inspect/command-individuals.vala new file mode 100644 index 0000000..e3ce6fd --- /dev/null +++ b/tools/inspect/command-individuals.vala @@ -0,0 +1,90 @@ +/* + * Copyright (C) 2010 Collabora Ltd. + * + * 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 . + * + * Authors: + * Philip Withnall + */ + +using Folks; +using Gee; +using GLib; + +private class Folks.Inspect.Commands.Individuals : Folks.Inspect.Command +{ + public override string name + { + get { return "individuals"; } + } + + public override string description + { + get + { + return "Inspect the individuals currently present in the aggregator"; + } + } + + public override string help + { + get + { + return "individuals List all known " + + "individuals.\n" + + "individuals [individual ID] Display the details of the " + + "specified individual and list its personas."; + } + } + + public Individuals (Client client) + { + base (client); + } + + public override void run (string? command_string) + { + if (command_string == null) + { + /* List all the individuals */ + this.client.aggregator.individuals.foreach ((k, v) => + { + Utils.print_individual ((Individual) v, false); + Utils.print_line (""); + }); + } + else + { + /* Display the details of a single individual */ + Individual individual = + this.client.aggregator.individuals.lookup (command_string); + + if (individual == null) + { + Utils.print_line ("Unrecognised individual ID '%s'.", + command_string); + return; + } + + Utils.print_individual (individual, true); + } + } + + public override string[]? complete_subcommand (string subcommand) + { + /* @subcommand should be an individual ID */ + return Readline.completion_matches (subcommand, + Utils.individual_id_completion_cb); + } +} diff --git a/tools/inspect/command-persona-stores.vala b/tools/inspect/command-persona-stores.vala new file mode 100644 index 0000000..445e75f --- /dev/null +++ b/tools/inspect/command-persona-stores.vala @@ -0,0 +1,107 @@ +/* + * Copyright (C) 2010 Collabora Ltd. + * + * 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 . + * + * Authors: + * Philip Withnall + */ + +using Folks; +using Gee; +using GLib; + +private class Folks.Inspect.Commands.PersonaStores : Folks.Inspect.Command +{ + public override string name + { + get { return "persona-stores"; } + } + + public override string description + { + get + { + return "Inspect the persona stores loaded by the aggregator"; + } + } + + public override string help + { + get + { + return "persona-stores List all known " + + "persona stores.\n" + + "persona-stores [persona store ID] Display the details of " + + "the specified persona store and list its personas."; + } + } + + public PersonaStores (Client client) + { + base (client); + } + + public override void run (string? command_string) + { + if (command_string == null) + { + /* List all the persona stores */ + Collection backends = + this.client.backend_store.list_backends (); + + foreach (Backend backend in backends) + { + HashTable stores = backend.persona_stores; + + stores.foreach ((k, v) => + { + Utils.print_persona_store ((PersonaStore) v, false); + Utils.print_line (""); + }); + } + } + else + { + /* Show the details of a particular persona store */ + Collection backends = + this.client.backend_store.list_backends (); + PersonaStore store = null; + + foreach (Backend backend in backends) + { + HashTable stores = backend.persona_stores; + store = stores.lookup (command_string); + if (store != null) + break; + } + + if (store == null) + { + Utils.print_line ("Unrecognised persona store ID '%s'.", + command_string); + return; + } + + Utils.print_persona_store (store, true); + } + } + + public override string[]? complete_subcommand (string subcommand) + { + /* @subcommand should be a persona store ID */ + return Readline.completion_matches (subcommand, + Utils.persona_store_id_completion_cb); + } +} diff --git a/tools/inspect/command-personas.vala b/tools/inspect/command-personas.vala new file mode 100644 index 0000000..87b953f --- /dev/null +++ b/tools/inspect/command-personas.vala @@ -0,0 +1,81 @@ +/* + * Copyright (C) 2010 Collabora Ltd. + * + * 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 . + * + * Authors: + * Philip Withnall + */ + +using Folks; +using Gee; +using GLib; + +private class Folks.Inspect.Commands.Personas : Folks.Inspect.Command +{ + public override string name + { + get { return "personas"; } + } + + public override string description + { + get + { + return "Inspect the personas currently present in the aggregator"; + } + } + + public override string help + { + get + { + return "personas List all known personas.\n" + + "personas [persona UID] Display the details of the " + + "specified persona."; + } + } + + public Personas (Client client) + { + base (client); + } + + public override void run (string? command_string) + { + this.client.aggregator.individuals.foreach ((k, v) => + { + Individual individual = (Individual) v; + + foreach (Persona persona in individual.personas) + { + /* Either list all personas, or only list the one specified */ + if (command_string != null && persona.uid != command_string) + continue; + + Utils.print_persona (persona); + + if (command_string == null) + Utils.print_line (""); + } + }); + } + + public override string[]? complete_subcommand (string subcommand) + { + /* @subcommand should be a persona UID */ + return Readline.completion_matches (subcommand, + Utils.persona_uid_completion_cb); + } +} diff --git a/tools/inspect/command-quit.vala b/tools/inspect/command-quit.vala new file mode 100644 index 0000000..7999507 --- /dev/null +++ b/tools/inspect/command-quit.vala @@ -0,0 +1,57 @@ +/* + * Copyright (C) 2010 Collabora Ltd. + * + * 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 . + * + * Authors: + * Philip Withnall + */ + +using Folks; +using GLib; + +private class Folks.Inspect.Commands.Quit : Folks.Inspect.Command +{ + public override string name + { + get { return "quit"; } + } + + public override string description + { + get + { + return "Quit the program."; + } + } + + public override string help + { + get + { + return "quit Quit the program gracefully, like a cow lolloping " + + "across a green field."; + } + } + + public Quit (Client client) + { + base (client); + } + + public override void run (string? command_string) + { + Process.exit (0); + } +} diff --git a/tools/inspect/command-signals.vala b/tools/inspect/command-signals.vala new file mode 100644 index 0000000..7030e46 --- /dev/null +++ b/tools/inspect/command-signals.vala @@ -0,0 +1,250 @@ +/* + * Copyright (C) 2010 Collabora Ltd. + * + * 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 . + * + * Authors: + * Philip Withnall + */ + +using Folks; +using Gee; +using GLib; + +/* + * signals — list signals we're currently connected to + * signals connect ClassName — connect to all the signals on all the instances + * of that class + * signals connect ClassName::signal — connect to the given signal on all the + * instances of that class + * signals connect 0xdeadbeef — connect to all the signals on a particular class + * instance + * signals connect 0xdeadbeef::signal — connect to the given signal on a + * particular class instance + * signals disconnect (as above) + * signals disconnect — signal handler ID + * signals ClassName — list all the signals on all the instances of that class, + * highlighting the ones we're currently connected to + * signals 0xdeadbeef — list all the signals on a particular class instance, + * highlighting the ones we're currently connected to + * signals ClassName::signal — show the details of this signal + * signals 0xdeadbeef::signal — show the details of this signal + */ + +private class Folks.Inspect.Commands.Signals : Folks.Inspect.Command +{ + public override string name + { + get { return "signals"; } + } + + public override string description + { + get + { + return "Allow connection to and display of signals emitted by " + + "libfolks."; + } + } + + public override string help + { + get + { + return "signals " + + "List signals we're currently connected to.\n" + + "signals connect [class name] " + + "Connect to all the signals on all the instances of that " + + "class.\n" + + "signals connect [class name]::[signal name] " + + "Connect to the given signal on all the instances of that " + + "class.\n" + + "signals connect [object pointer] " + + "Connect to all the signals on a particular class instance.\n" + + "signals connect [object pointer]::[signal name] " + + "Connect to the given signal on a particular class instance.\n" + + "signals disconnect " + + "(As for 'connect'.)\n" + + "signals [class name] " + + "List all the signals on all the instances of that class, " + + "highlighting the ones we're currently connected to.\n" + + "signals [object pointer] " + + "List all the signals on a particular class instance, " + + "highlighting the ones we're currently connected to.\n" + + "signals [class name]::[signal name] " + + "Show the details of this signal.\n" + + "signals [object pointer]::[signal name] " + + "Show the details of this signal."; + } + } + + public Signals (Client client) + { + base (client); + } + + public override void run (string? command_string) + { + if (command_string == null) + { + /* List all the signals we're connected to */ + this.client.signal_manager.list_signals (Type.INVALID, null); + } + else + { + /* Parse subcommands */ + string[] parts = command_string.split (" ", 2); + + if (parts.length < 1) + { + Utils.print_line ("Unrecognised 'signals' command '%s'.", + command_string); + return; + } + + Type class_type; + Object class_instance; + string signal_name; + string detail_string; + + if (parts[0] == "connect" || parts[0] == "disconnect") + { + /* Connect to or disconnect from a signal */ + if (parts[1] == null || parts[1].strip () == "") + { + Utils.print_line ("Unrecognised signal identifier '%s'.", + parts[1]); + return; + } + + if (this.parse_signal_id (parts[1].strip (), out class_type, + out class_instance, out signal_name, + out detail_string) == false) + { + return; + } + + /* FIXME: Handle "disconnect " */ + if (parts[0] == "connect") + { + uint signal_count = + this.client.signal_manager.connect_to_signal (class_type, + class_instance, signal_name, detail_string); + Utils.print_line ("Connected to %u signals.", signal_count); + } + else + { + uint signal_count = + this.client.signal_manager.disconnect_from_signal ( + class_type, class_instance, signal_name, + detail_string); + Utils.print_line ("Disconnected from %u signals.", + signal_count); + } + } + else + { + /* List some of the signals we're connected to, or display + * their details. */ + if (this.parse_signal_id (parts[0].strip (), out class_type, + out class_instance, out signal_name, + out detail_string) == false) + { + return; + } + + if (signal_name == null) + { + this.client.signal_manager.list_signals (class_type, + class_instance); + } + else + { + /* Get the class type from the instance */ + if (class_type == Type.INVALID) + class_type = class_instance.get_type (); + + this.client.signal_manager.show_signal_details (class_type, + signal_name, detail_string); + } + } + } + } + + public override string[]? complete_subcommand (string subcommand) + { + /* @subcommand should be a backend name */ + /* TODO */ + return Readline.completion_matches (subcommand, + Utils.backend_name_completion_cb); + } + + private bool parse_signal_id (string input, + out Type class_type, + out Object? class_instance, + out string? signal_name, + out string? detail_string) + { + /* We accept any of the following formats: + * ClassName::signal-name + * ClassName::signal-name::detail + * 0xdeadbeef::signal-name + * 0xdeadbeef::signal-name::detail + * ClassName + * 0xdeadbeef + * + * We output exactly one of class_type and class_instance, and optionally + * output signal_name and/or detail_string as appropriate. + */ + assert (input != null && input != ""); + + string[] parts = input.split ("::", 3); + string class_name_or_instance = parts[0]; + string signal_name_inner = (parts.length > 1) ? parts[1] : null; + string detail_string_inner = (parts.length > 2) ? parts[2] : null; + + if (signal_name == "" || detail_string == "") + { + Utils.print_line ("Invalid signal identifier '%s'.", input); + return false; + } + + if (class_name_or_instance.length > 2 && + class_name_or_instance[0] == '0' && class_name_or_instance[1] == 'x') + { + /* We have a class instance */ + ulong address = class_name_or_instance.to_ulong (null, 16); + class_instance = (Object) address; + assert (class_instance.get_type ().is_object ()); + } + else + { + /* We have a class name */ + class_type = Type.from_name (class_name_or_instance); + if (class_type == Type.INVALID || + (class_type.is_instantiatable () == false && + class_type.is_interface () == false)) + { + Utils.print_line ("Unrecognised class name '%s'.", + class_name_or_instance); + return false; + } + } + + signal_name = signal_name_inner; + detail_string = detail_string_inner; + + return true; + } +} diff --git a/tools/inspect/inspect.vala b/tools/inspect/inspect.vala new file mode 100644 index 0000000..57cf571 --- /dev/null +++ b/tools/inspect/inspect.vala @@ -0,0 +1,223 @@ +/* + * Copyright (C) 2010 Collabora Ltd. + * + * 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 . + * + * Authors: + * Philip Withnall + */ + +using Folks.Inspect.Commands; +using Folks; +using Readline; +using Gee; +using GLib; + +/* 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 commands; + private MainLoop main_loop; + private unowned Thread folks_thread; + public IndividualAggregator aggregator { get; private set; } + public BackendStore backend_store { get; private set; } + public SignalManager signal_manager { get; private set; } + + public static int main (string[] args) + { + main_client = new Client (); + main_client.run_interactive (); + + return 0; + } + + private void *folks_thread_main () + { + this.main_loop = new MainLoop (); + + this.signal_manager = new SignalManager (); + + this.aggregator = new IndividualAggregator (); + this.aggregator.prepare (); + + this.backend_store = BackendStore.dup (); + this.backend_store.backend_available.connect ((bs, b) => + { + Backend backend = (Backend) b; + + backend.prepare.begin ((obj, result) => + { + try + { + backend.prepare.end (result); + } + catch (GLib.Error e) + { + warning ("Error preparing Backend '%s': %s", backend.name, + e.message); + } + }); + }); + + this.backend_store.load_backends (); + + this.main_loop.run (); + + return null; + } + + public Client () + { + Utils.init (); + + this.commands = new HashMap (str_hash, str_equal); + + /* 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 ("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 ("signals", new Commands.Signals (this)); + + try + { + this.folks_thread = Thread.create ( + this.folks_thread_main, true); + } + catch (ThreadError e) + { + stdout.printf ("Couldn't create aggregator thread: %s", e.message); + Process.exit (1); + } + } + + public void run_interactive () + { + /* Interactive mode: have a little shell which allows the data from + * libfolks to be browsed and edited in real time. */ + + /* 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; + + /* Main prompt loop */ + while (true) + { + string command_line = Readline.readline ("> "); + + if (command_line == null) + continue; + + command_line = command_line.strip (); + if (command_line == "") + continue; + + string subcommand; + string command_name; + Command command = this.parse_command_line (command_line, + out command_name, out subcommand); + + /* Run the command */ + if (command != null) + command.run (subcommand); + else + stdout.printf ("Unrecognised command '%s'.\n", command_name); + + /* Store the command in the history, even if it failed */ + Readline.History.add (command_line); + } + } + + private static Command? parse_command_line (string command_line, + out string command_name, + out string? subcommand) + { + 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; + } +} + +private abstract class Folks.Inspect.Command +{ + protected Client client; + + public Command (Client client) + { + this.client = client; + } + + public abstract string name { get; } + public abstract string description { get; } + public abstract string help { get; } + + public abstract void run (string? command_string); + + [CCode (array_length = false, array_null_terminated = true)] + public virtual string[]? complete_subcommand (string subcommand) + { + /* Default implementation */ + return null; + } +} diff --git a/tools/inspect/signal-manager.vala b/tools/inspect/signal-manager.vala new file mode 100644 index 0000000..746e02e --- /dev/null +++ b/tools/inspect/signal-manager.vala @@ -0,0 +1,502 @@ +/* + * Copyright (C) 2010 Collabora Ltd. + * + * 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 . + * + * Authors: + * Philip Withnall + */ + +using Folks; +using Gee; +using GLib; + +public class Folks.Inspect.SignalManager : Object +{ + /* Map from class type → map from signal ID to hook ID */ + private HashMap> signals_by_class_type; + /* Map from class instance → map from signal ID to hook ID */ + private HashMap> signals_by_class_instance; + + public SignalManager () + { + this.signals_by_class_type = + new HashMap> (); + this.signals_by_class_instance = + new HashMap> (); + } + + public void list_signals (Type class_type, + Object? class_instance) + { + if (class_type != Type.INVALID) + { + /* List the signals we're connected to via emission hooks on this + * class type */ + HashMap hook_ids = + this.signals_by_class_type.get (class_type); + + Utils.print_line ("Signals on all instances of class type '%s':", + class_type.name ()); + Utils.indent (); + this.list_signals_for_type (class_type, hook_ids); + Utils.unindent (); + } + else if (class_instance != null) + { + /* List the signals we're connected to on this class instance */ + HashMap signal_handler_ids = + this.signals_by_class_instance.get (class_instance); + + Utils.print_line ("Signals on instance %p of class type '%s':", + class_instance, class_instance.get_type ().name ()); + Utils.indent (); + this.list_signals_for_type (class_instance.get_type (), + signal_handler_ids); + Utils.unindent (); + } + else + { + /* List all the signals we're connected to on everything */ + MapIterator> class_type_iter = + this.signals_by_class_type.map_iterator (); + + Utils.print_line ("Connected signals on all instances of classes:"); + + Utils.indent (); + while (class_type_iter.next () == true) + { + HashMap hook_ids = class_type_iter.get_value (); + MapIterator hook_iter = hook_ids.map_iterator (); + + string class_name = class_type_iter.get_key ().name (); + while (hook_iter.next () == true) + { + Utils.print_line ("%s::%s — connected", class_name, + Signal.name (hook_iter.get_key ())); + } + } + Utils.unindent (); + + MapIterator> class_instance_iter = + this.signals_by_class_instance.map_iterator (); + + Utils.print_line ("Connected signals on specific instances of " + + "classes:"); + + Utils.indent (); + while (class_instance_iter.next () == true) + { + HashMap signal_handler_ids = + class_instance_iter.get_value (); + MapIterator signal_handler_iter = + signal_handler_ids.map_iterator (); + + string class_name = + class_instance_iter.get_key ().get_type ().name (); + while (signal_handler_iter.next () == true) + { + Utils.print_line ("%s::%s — connected", class_name, + Signal.name (signal_handler_iter.get_key ())); + } + } + Utils.unindent (); + } + } + + public void show_signal_details (Type class_type, + string? signal_name, + string? detail_string) + { + uint signal_id = Signal.lookup (signal_name, class_type); + if (signal_id == 0) + { + Utils.print_line ("Unrecognised signal name '%s' on class '%s'.", + signal_name, class_type.name ()); + return; + } + + /* Query the signal's information */ + SignalQuery query_info; + Signal.query (signal_id, out query_info); + + /* Print the query response */ + Utils.print_line ("Signal ID %u", query_info.signal_id); + Utils.print_line ("Signal name %s", query_info.signal_name); + Utils.print_line ("Emitting type %s", query_info.itype.name ()); + Utils.print_line ("Signal flags %s", + this.signal_flags_to_string (query_info.signal_flags)); + Utils.print_line ("Return type %s", query_info.return_type.name ()); + Utils.print_line ("Parameter types:"); + Utils.indent (); + for (uint i = 0; i < query_info.n_params; i++) + Utils.print_line ("%-4u %s", i, query_info.param_types[i].name ()); + Utils.unindent (); + } + + public uint connect_to_signal (Type class_type, + Object? class_instance, + string? signal_name, + string? detail_string) + { + /* We return the number of signals we connected to */ + if (class_type != Type.INVALID && signal_name != null) + { + /* Connecting to a given signal on all instances of a class */ + uint signal_id = Signal.lookup (signal_name, class_type); + if (signal_id == 0) + { + Utils.print_line ("Unrecognised signal name '%s' on class '%s'.", + signal_name, class_type.name ()); + return 0; + } + + if (this.add_emission_hook (class_type, signal_id, + detail_string) == false) + { + Utils.print_line ("Not allowed to connect to signal '%s' on " + + "class '%s'.", signal_name, class_type.name ()); + return 0; + } + + return 1; + } + else if (class_type != Type.INVALID && signal_name == null) + { + /* Connecting to all signals on all instances of a class */ + uint[] signal_ids = Signal.list_ids (class_type); + uint signal_count = 0; + + foreach (uint signal_id in signal_ids) + { + if (this.add_emission_hook (class_type, signal_id, null) == true) + signal_count++; + } + + return signal_count; + } + else if (class_instance != null && signal_name != null) + { + /* Connecting to a given signal on a given class instance */ + uint signal_id = + Signal.lookup (signal_name, class_instance.get_type ()); + if (signal_id == 0) + { + Utils.print_line ("Unrecognised signal name '%s' on instance " + + "%p of class '%s'.", signal_name, class_instance, + class_instance.get_type ().name ()); + return 0; + } + + this.add_signal_handler (class_instance, signal_id, detail_string); + + return 1; + } + else if (class_instance != null && signal_name == null) + { + /* Connecting to all signals on a given class instance */ + uint[] signal_ids = Signal.list_ids (class_instance.get_type ()); + uint signal_count = 0; + + foreach (uint signal_id in signal_ids) + { + signal_count++; + this.add_signal_handler (class_instance, signal_id, null); + } + + return signal_count; + } + + assert_not_reached (); + } + + public uint disconnect_from_signal (Type class_type, + Object? class_instance, + string? signal_name, + string? detail_string) + { + /* We return the number of signals we disconnected from */ + if (class_type != Type.INVALID && signal_name != null) + { + /* Disconnecting from a given signal on all instances of a class */ + uint signal_id = Signal.lookup (signal_name, class_type); + if (signal_id == 0) + { + Utils.print_line ("Unrecognised signal name '%s' on class '%s'.", + signal_name, class_type.name ()); + return 0; + } + + if (this.remove_emission_hook (class_type, signal_id) == false) + { + Utils.print_line ("Could not remove hook for signal '%s' on " + + "class '%s'.", signal_name, class_type.name ()); + return 0; + } + + return 1; + } + else if (class_type != Type.INVALID && signal_name == null) + { + /* Disconnecting from all signals on all instances of a class */ + uint[] signal_ids = Signal.list_ids (class_type); + uint signal_count = 0; + + foreach (uint signal_id in signal_ids) + { + if (this.remove_emission_hook (class_type, signal_id) == true) + signal_count--; + } + + return signal_count; + } + else if (class_instance != null && signal_name != null) + { + /* Disconnecting from a given signal on a given class instance */ + uint signal_id = + Signal.lookup (signal_name, class_instance.get_type ()); + if (signal_id == 0) + { + Utils.print_line ("Unrecognised signal name '%s' on instance " + + "%p of class '%s'.", signal_name, class_instance, + class_instance.get_type ().name ()); + return 0; + } + + this.remove_signal_handler (class_instance, signal_id); + + return 1; + } + else if (class_instance != null && signal_name == null) + { + /* Disconnecting from all signals on a given class instance */ + uint[] signal_ids = Signal.list_ids (class_instance.get_type ()); + uint signal_count = 0; + + foreach (uint signal_id in signal_ids) + { + if (this.remove_signal_handler (class_instance, signal_id)) + signal_count--; + } + + return signal_count; + } + + assert_not_reached (); + } + + private void list_signals_for_type (Type type, + HashMap? signal_id_map) + { + uint[] signal_ids = Signal.list_ids (type); + + /* Print information about the signals on this type */ + if (signal_ids != null) + { + string type_name = type.name (); + foreach (uint signal_id in signal_ids) + { + unowned string signal_name = Signal.name (signal_id); + + if (signal_id_map != null && + signal_id_map.has_key (signal_id) == true) + { + Utils.print_line ("%s::%s — connected", + type_name, signal_name); + } + else + { + Utils.print_line ("%s::%s", + type_name, signal_name); + } + } + } + + /* Recurse to the type's interfaces */ + Type[] interfaces = type.interfaces (); + foreach (Type interface_type in interfaces) + this.list_signals_for_type (interface_type, signal_id_map); + + /* Chain up to the type's parent */ + Type parent_type = type.parent (); + if (parent_type != Type.INVALID) + this.list_signals_for_type (parent_type, signal_id_map); + } + + /* FIXME: This is necessary because if we do sizeof(Closure), Vala will + * generate the following C code: sizeof(GClosure*). + * This is not what we want. */ + [CCode (cname = "sizeof (GClosure)")] extern const int CLOSURE_STRUCT_SIZE; + + private void add_signal_handler (Object class_instance, + uint signal_id, + string? detail_string) + { + Closure closure = new Closure (this.CLOSURE_STRUCT_SIZE, this); + closure.set_meta_marshal (null, this.signal_meta_marshaller); + + Quark detail_quark = 0; + if (detail_string != null) + detail_quark = Quark.try_string (detail_string); + + ulong signal_handler_id = Signal.connect_closure_by_id (class_instance, + signal_id, detail_quark, closure, false); + + /* Store the signal handler ID so we can list or remove it later */ + HashMap signal_handler_ids = + this.signals_by_class_instance.get (class_instance); + if (signal_handler_ids == null) + { + signal_handler_ids = new HashMap (); + this.signals_by_class_instance.set (class_instance, + signal_handler_ids); + } + + signal_handler_ids.set (signal_id, signal_handler_id); + } + + private bool remove_signal_handler (Object class_instance, + uint signal_id) + { + HashMap signal_handler_ids = + this.signals_by_class_instance.get (class_instance); + + if (signal_handler_ids == null || + signal_handler_ids.has_key (signal_id) == false) + { + return false; + } + + ulong signal_handler_id = signal_handler_ids.get (signal_id); + SignalHandler.disconnect (class_instance, signal_handler_id); + signal_handler_ids.unset (signal_id); + + return true; + } + + private static void signal_meta_marshaller (Closure closure, + out Value return_value, + Value[] param_values, + void *invocation_hint, + void *marshal_data) + { + SignalInvocationHint* hint = (SignalInvocationHint*) invocation_hint; + + SignalQuery query_info; + Signal.query (hint->signal_id, out query_info); + + Utils.print_line ("Signal '%s::%s' emitted with parameters:", + query_info.itype.name (), query_info.signal_name); + + Utils.indent (); + uint i = 0; + foreach (Value param_value in param_values) + { + Utils.print_line ("%-4u %-10s %s", i++, param_value.type ().name (), + Utils.transform_value_to_string (param_value)); + } + Utils.unindent (); + } + + private bool add_emission_hook (Type class_type, + uint signal_id, + string? detail_string) + { + Quark detail_quark = 0; + if (detail_string != null) + detail_quark = Quark.try_string (detail_string); + + /* Query the signal to check it supports emission hooks */ + SignalQuery query; + Signal.query (signal_id, out query); + + /* FIXME: It would be nice if we could find some way to support NO_HOOKS + * signals. */ + if ((query.signal_flags & SignalFlags.NO_HOOKS) != 0) + return false; + + ulong hook_id = Signal.add_emission_hook (signal_id, + detail_quark, this.emission_hook_cb, null); + + /* Store the hook ID so we can list or remove it later */ + HashMap hook_ids = + this.signals_by_class_type.get (class_type); + if (hook_ids == null) + { + hook_ids = new HashMap (); + this.signals_by_class_type.set (class_type, hook_ids); + } + + hook_ids.set (signal_id, hook_id); + + return true; + } + + private bool remove_emission_hook (Type class_type, + uint signal_id) + { + HashMap hook_ids = + this.signals_by_class_type.get (class_type); + + if (hook_ids == null || hook_ids.has_key (signal_id) == false) + return false; + + ulong hook_id = hook_ids.get (signal_id); + Signal.remove_emission_hook (signal_id, hook_id); + hook_ids.unset (signal_id); + + return true; + } + + private bool emission_hook_cb (SignalInvocationHint hint, + Value[] param_values) + { + SignalQuery query_info; + Signal.query (hint.signal_id, out query_info); + + Utils.print_line ("Signal '%s::%s' emitted with parameters:", + query_info.itype.name (), query_info.signal_name); + + Utils.indent (); + uint i = 0; + foreach (Value param_value in param_values) + { + Utils.print_line ("%-4u %-10s %s", i++, param_value.type ().name (), + Utils.transform_value_to_string (param_value)); + } + Utils.unindent (); + + return true; + } + + private static string signal_flags_to_string (SignalFlags flags) + { + string output = ""; + + if ((flags & SignalFlags.RUN_FIRST) != 0) + output += "G_SIGNAL_RUN_FIRST"; + if ((flags & SignalFlags.RUN_LAST) != 0) + output += ((output != "") ? " | " : "") + "G_SIGNAL_RUN_LAST"; + if ((flags & SignalFlags.RUN_CLEANUP) != 0) + output += ((output != "") ? " | " : "") + "G_SIGNAL_RUN_CLEANUP"; + if ((flags & SignalFlags.DETAILED) != 0) + output += ((output != "") ? " | " : "") + "G_SIGNAL_DETAILED"; + if ((flags & SignalFlags.ACTION) != 0) + output += ((output != "") ? " | " : "") + "G_SIGNAL_ACTION"; + if ((flags & SignalFlags.NO_HOOKS) != 0) + output += ((output != "") ? " | " : "") + "G_SIGNAL_NO_HOOKS"; + + return output; + } +} diff --git a/tools/inspect/utils.vala b/tools/inspect/utils.vala new file mode 100644 index 0000000..725006d --- /dev/null +++ b/tools/inspect/utils.vala @@ -0,0 +1,477 @@ +/* + * Copyright (C) 2010 Collabora Ltd. + * + * 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 . + * + * Authors: + * Philip Withnall + */ + +using Folks; +using Gee; +using GLib; + +private class Folks.Inspect.Utils +{ + /* The current indentation level, in spaces */ + private static uint indentation = 0; + private static string indentation_string = ""; + + public static void init () + { + Utils.indentation_string = ""; + + /* Register some general transformation functions */ + Value.register_transform_func (typeof (Object), typeof (string), + Utils.transform_object_to_string); + Value.register_transform_func (typeof (Folks.PersonaStore), + typeof (string), Utils.transform_persona_store_to_string); + Value.register_transform_func (typeof (string[]), typeof (string), + Utils.transform_string_array_to_string); + } + + private static void transform_object_to_string (Value src, + out Value dest) + { + /* FIXME: works around bgo#638363 */ + Value dest_tmp = Value (typeof (string)); + dest_tmp.take_string ("%p".printf (src.get_object ())); + dest = dest_tmp; + } + + private static void transform_persona_store_to_string (Value src, + out Value dest) + { + /* FIXME: works around bgo#638363 */ + Value dest_tmp = Value (typeof (string)); + Folks.PersonaStore store = (Folks.PersonaStore) src.get_object (); + dest_tmp.take_string ("%p: %s, %s (%s)".printf (store, store.type_id, + store.id, store.display_name)); + dest = dest_tmp; + } + + private static void transform_string_array_to_string (Value src, + out Value dest) + { + /* FIXME: works around bgo#638363 */ + Value dest_tmp = Value (typeof (string)); + unowned string[] array = (string[]) src.get_boxed (); + string output = "{ "; + bool first = true; + foreach (string element in array) + { + if (first == false) + output += ", "; + output += "'%s'".printf (element); + first = false; + } + output += " }"; + dest_tmp.take_string (output); + dest = dest_tmp; + } + + public static void indent () + { + /* We indent in increments of two spaces */ + Utils.indentation += 2; + Utils.indentation_string = string.nfill (Utils.indentation, ' '); + } + + public static void unindent () + { + Utils.indentation -= 2; + Utils.indentation_string = string.nfill (Utils.indentation, ' '); + } + + [PrintfFormat ()] + public static void print_line (string format, ...) + { + /* FIXME: store the va_list temporarily to work around bgo#638308 */ + var valist = va_list (); + string output = format.vprintf (valist); + stdout.printf ("%s%s\n", Utils.indentation_string, output); + } + + public static void print_individual (Individual individual, + bool show_personas) + { + Utils.print_line ("Individual '%s' with %u personas:", + individual.id, individual.personas.length ()); + + /* List the Individual's properties */ + unowned ParamSpec[] properties = + individual.get_class ().list_properties (); + + Utils.indent (); + foreach (unowned ParamSpec pspec in properties) + { + Value prop_value; + string output_string; + + /* Ignore the personas property if we're printing the personas out */ + if (show_personas == true && pspec.get_name () == "personas") + continue; + + prop_value = Value (pspec.value_type); + individual.get_property (pspec.get_name (), ref prop_value); + + output_string = Utils.property_to_string (individual.get_type (), + pspec.get_name (), prop_value); + + Utils.print_line ("%-20s %s", pspec.get_nick (), output_string); + } + + if (show_personas == true) + { + Utils.print_line (""); + Utils.print_line ("Personas:"); + + Utils.indent (); + foreach (Persona persona in individual.personas) + Utils.print_persona (persona); + Utils.unindent (); + } + Utils.unindent (); + } + + public static void print_persona (Persona persona) + { + Utils.print_line ("Persona '%s':", persona.uid); + + /* List the Persona's properties */ + unowned ParamSpec[] properties = + persona.get_class ().list_properties (); + + Utils.indent (); + foreach (unowned ParamSpec pspec in properties) + { + Value prop_value; + string output_string; + + prop_value = Value (pspec.value_type); + persona.get_property (pspec.get_name (), ref prop_value); + + output_string = Utils.property_to_string (persona.get_type (), + pspec.get_name (), prop_value); + + Utils.print_line ("%-20s %s", pspec.get_nick (), output_string); + } + Utils.unindent (); + } + + public static void print_persona_store (PersonaStore store, + bool show_personas) + { + Utils.print_line ("Persona store '%s' with %u personas:", + store.id, store.personas.size ()); + + /* List the store's properties */ + unowned ParamSpec[] properties = + store.get_class ().list_properties (); + + Utils.indent (); + foreach (unowned ParamSpec pspec in properties) + { + Value prop_value; + string output_string; + + /* Ignore the personas property if we're printing the personas out */ + if (show_personas == true && pspec.get_name () == "personas") + continue; + + prop_value = Value (pspec.value_type); + store.get_property (pspec.get_name (), ref prop_value); + + output_string = Utils.property_to_string (store.get_type (), + pspec.get_name (), prop_value); + + Utils.print_line ("%-20s %s", pspec.get_nick (), output_string); + } + + if (show_personas == true) + { + Utils.print_line (""); + Utils.print_line ("Personas:"); + + Utils.indent (); + store.personas.foreach ((k, v) => + { + Utils.print_persona ((Persona) v); + }); + Utils.unindent (); + } + Utils.unindent (); + } + + private static string property_to_string (Type object_type, + string prop_name, + Value prop_value) + { + string output_string; + + /* Overrides for various known properties */ + if (object_type.is_a (typeof (Individual)) && prop_name == "personas") + { + unowned GLib.List personas = + (GLib.List) prop_value.get_pointer (); + return "List of %u personas".printf (personas.length ()); + } + else if (object_type.is_a (typeof (PersonaStore)) && + prop_name == "personas") + { + unowned HashTable personas = + (HashTable) prop_value.get_boxed (); + return "Set of %u personas".printf (personas.size ()); + } + else if (prop_name == "groups") + { + HashTable groups = + (HashTable) prop_value.get_boxed (); + output_string = "{ "; + bool first = true; + + /* FIXME: This is rather inefficient */ + groups.foreach ((k, v) => + { + if ((bool) v == true) + { + if (first == false) + output_string += ", "; + output_string += "'%s'".printf ((string) k); + first = false; + } + }); + + output_string += " }"; + return output_string; + } + else if (prop_name == "avatar") + { + return "%p".printf (prop_value.get_object ()); + } + else if (prop_name == "im-addresses") + { + HashTable> im_addresses = + (HashTable>) prop_value.get_boxed (); + output_string = "{ "; + bool first = true; + + /* FIXME: This is rather inefficient */ + im_addresses.foreach ((k, v) => + { + if (first == false) + output_string += ", "; + output_string += "'%s' : { ".printf ((string) k); + first = false; + + GenericArray addresses = (GenericArray) v; + bool _first = true; + addresses.foreach ((a) => + { + if (_first == false) + output_string += ", "; + output_string += "'%s'".printf ((string) a); + _first = false; + }); + + output_string += " }"; + }); + + output_string += " }"; + return output_string; + } + + return Utils.transform_value_to_string (prop_value); + } + + public static string transform_value_to_string (Value prop_value) + { + if (Value.type_transformable (prop_value.type (), typeof (string))) + { + /* Convert to a string value */ + Value string_value = Value (typeof (string)); + prop_value.transform (ref string_value); + return string_value.get_string (); + } + else + { + /* Can't convert the property value to a string */ + return "Can't convert from type '%s' to '%s'".printf ( + prop_value.type ().name (), typeof (string).name ()); + } + } + + /* FIXME: This can't be in the command_completion_cb() function because Vala + * doesn't allow static local variables. Erk. */ + private static MapIterator? command_name_iter = null; + + /* Complete a command name, starting with @word. */ + public static string? command_name_completion_cb (string word, + int state) + { + /* Initialise state. Whoever wrote the readline API should be shot. */ + if (state == 0) + Utils.command_name_iter = main_client.commands.map_iterator (); + + while (Utils.command_name_iter.next () == true) + { + string command_name = Utils.command_name_iter.get_key (); + if (command_name.has_prefix (word)) + return command_name; + } + + /* Clean up */ + Utils.command_name_iter = null; + return null; + } + + /* FIXME: This can't be in the individual_id_completion_cb() function because + * Vala doesn't allow static local variables. Erk. */ + private static HashTableIter? individual_id_iter = null; + + /* Complete an individual's ID, starting with @word. */ + public static string? individual_id_completion_cb (string word, + int state) + { + /* Initialise state. Whoever wrote the readline API should be shot. */ + if (state == 0) + { + Utils.individual_id_iter = HashTableIter ( + main_client.aggregator.individuals); + } + + string id; + Individual individual; + while (Utils.individual_id_iter.next (out id, out individual) == true) + { + if (id.has_prefix (word)) + return id; + } + + /* Clean up */ + Utils.individual_id_iter = null; + return null; + } + + /* FIXME: This can't be in the individual_id_completion_cb() function because + * Vala doesn't allow static local variables. Erk. */ + private static unowned GLib.List? persona_uid_iter = null; + + /* Complete an individual's ID, starting with @word. */ + public static string? persona_uid_completion_cb (string word, + int state) + { + /* Initialise state. Whoever wrote the readline API should be shot. */ + if (state == 0) + { + Utils.individual_id_iter = HashTableIter ( + main_client.aggregator.individuals); + Utils.persona_uid_iter = null; + } + + Individual individual = null; + while (Utils.persona_uid_iter != null || + Utils.individual_id_iter.next (null, out individual) == true) + { + if (Utils.persona_uid_iter == null) + { + assert (individual != null); + Utils.persona_uid_iter = individual.personas; + } + + while (Utils.persona_uid_iter != null) + { + unowned Persona persona = (Persona) Utils.persona_uid_iter.data; + Utils.persona_uid_iter = Utils.persona_uid_iter.next; + if (persona.uid.has_prefix (word)) + return persona.uid; + } + } + + /* Clean up */ + Utils.individual_id_iter = null; + return null; + } + + /* FIXME: This can't be in the backend_name_completion_cb() function because + * Vala doesn't allow static local variables. Erk. */ + private static Iterator? backend_name_iter = null; + + /* Complete an individual's ID, starting with @word. */ + public static string? backend_name_completion_cb (string word, + int state) + { + /* Initialise state. Whoever wrote the readline API should be shot. */ + if (state == 0) + { + Utils.backend_name_iter = + main_client.backend_store.list_backends ().iterator (); + } + + while (Utils.backend_name_iter.next () == true) + { + Backend backend = Utils.backend_name_iter.get (); + if (backend.name.has_prefix (word)) + return backend.name; + } + + /* Clean up */ + Utils.backend_name_iter = null; + return null; + } + + /* FIXME: This can't be in the persona_store_id_completion_cb() function + * because Vala doesn't allow static local variables. Erk. */ + private static HashTableIter? persona_store_id_iter = + null; + + /* Complete a persona store's ID, starting with @word. */ + public static string? persona_store_id_completion_cb (string word, + int state) + { + /* Initialise state. Whoever wrote the readline API should be shot. */ + if (state == 0) + { + Utils.backend_name_iter = + main_client.backend_store.list_backends ().iterator (); + Utils.persona_store_id_iter = null; + } + + while (Utils.persona_store_id_iter != null || + Utils.backend_name_iter.next () == true) + { + if (Utils.persona_store_id_iter == null) + { + Backend backend = Utils.backend_name_iter.get (); + Utils.persona_store_id_iter = + HashTableIter (backend.persona_stores); + } + + string id; + PersonaStore store; + while (Utils.persona_store_id_iter.next (out id, out store) == true) + { + if (id.has_prefix (word)) + return id; + } + + /* Clean up */ + Utils.persona_store_id_iter = null; + } + + /* Clean up */ + Utils.backend_name_iter = null; + return null; + } +}