summaryrefslogtreecommitdiff
path: root/tools
diff options
context:
space:
mode:
authorPhilip Withnall <philip.withnall@collabora.co.uk>2010-09-06 19:38:21 +0100
committerTravis Reitter <travis.reitter@collabora.co.uk>2010-12-30 09:25:28 -0800
commit96b1a7b28989b50cc0bb2241ece5889a2b7fb0cf (patch)
treeb620e8c10cbe25e40e5568f1c6ba9f2cac83b741 /tools
parent98772a1dce8cf8d47c6a1c942e42dafaa156a700 (diff)
Bug 629075 — Add folks command line application
Add an interactive command line inspection utility for libfolks. Closes: bgo#629075.
Diffstat (limited to 'tools')
-rw-r--r--tools/Makefile.am8
-rw-r--r--tools/inspect/Makefile.am40
-rw-r--r--tools/inspect/command-backends.vala102
-rw-r--r--tools/inspect/command-help.vala89
-rw-r--r--tools/inspect/command-individuals.vala90
-rw-r--r--tools/inspect/command-persona-stores.vala107
-rw-r--r--tools/inspect/command-personas.vala81
-rw-r--r--tools/inspect/command-quit.vala57
-rw-r--r--tools/inspect/command-signals.vala250
-rw-r--r--tools/inspect/inspect.vala223
-rw-r--r--tools/inspect/signal-manager.vala502
-rw-r--r--tools/inspect/utils.vala477
12 files changed, 2026 insertions, 0 deletions
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 <http://www.gnu.org/licenses/>.
+ *
+ * Authors:
+ * Philip Withnall <philip.withnall@collabora.co.uk>
+ */
+
+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<Backend> 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 <http://www.gnu.org/licenses/>.
+ *
+ * Authors:
+ * Philip Withnall <philip.withnall@collabora.co.uk>
+ */
+
+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 <command>' for more information " +
+ "about a particular command.");
+
+ MapIterator<string, Command> 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 <http://www.gnu.org/licenses/>.
+ *
+ * Authors:
+ * Philip Withnall <philip.withnall@collabora.co.uk>
+ */
+
+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 <http://www.gnu.org/licenses/>.
+ *
+ * Authors:
+ * Philip Withnall <philip.withnall@collabora.co.uk>
+ */
+
+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<Backend> backends =
+ this.client.backend_store.list_backends ();
+
+ foreach (Backend backend in backends)
+ {
+ HashTable<string, PersonaStore> 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<Backend> backends =
+ this.client.backend_store.list_backends ();
+ PersonaStore store = null;
+
+ foreach (Backend backend in backends)
+ {
+ HashTable<string, PersonaStore> 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 <http://www.gnu.org/licenses/>.
+ *
+ * Authors:
+ * Philip Withnall <philip.withnall@collabora.co.uk>
+ */
+
+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 <http://www.gnu.org/licenses/>.
+ *
+ * Authors:
+ * Philip Withnall <philip.withnall@collabora.co.uk>
+ */
+
+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 <http://www.gnu.org/licenses/>.
+ *
+ * Authors:
+ * Philip Withnall <philip.withnall@collabora.co.uk>
+ */
+
+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 <signal ID>" */
+ 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 <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;
+
+/* 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 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<string, Command> (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<void*>.create<void*> (
+ 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 <http://www.gnu.org/licenses/>.
+ *
+ * Authors:
+ * Philip Withnall <philip.withnall@collabora.co.uk>
+ */
+
+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<Type, HashMap<uint, ulong>> signals_by_class_type;
+ /* Map from class instance → map from signal ID to hook ID */
+ private HashMap<Object, HashMap<uint, ulong>> signals_by_class_instance;
+
+ public SignalManager ()
+ {
+ this.signals_by_class_type =
+ new HashMap<Type, HashMap<uint, ulong>> ();
+ this.signals_by_class_instance =
+ new HashMap<Object, HashMap<uint, ulong>> ();
+ }
+
+ 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<uint, ulong> 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<uint, ulong> 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<Type, HashMap<uint, ulong>> 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<uint, ulong> hook_ids = class_type_iter.get_value ();
+ MapIterator<uint, ulong> 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<Object, HashMap<uint, ulong>> 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<uint, ulong> signal_handler_ids =
+ class_instance_iter.get_value ();
+ MapIterator<uint, ulong> 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<uint, ulong>? 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<uint, ulong> signal_handler_ids =
+ this.signals_by_class_instance.get (class_instance);
+ if (signal_handler_ids == null)
+ {
+ signal_handler_ids = new HashMap<uint, ulong> ();
+ 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<uint, ulong> 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<uint, ulong> hook_ids =
+ this.signals_by_class_type.get (class_type);
+ if (hook_ids == null)
+ {
+ hook_ids = new HashMap<uint, ulong> ();
+ 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<uint, ulong> 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 <http://www.gnu.org/licenses/>.
+ *
+ * Authors:
+ * Philip Withnall <philip.withnall@collabora.co.uk>
+ */
+
+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<Persona> personas =
+ (GLib.List<Persona>) 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<string, Persona> personas =
+ (HashTable<string, Persona>) prop_value.get_boxed ();
+ return "Set of %u personas".printf (personas.size ());
+ }
+ else if (prop_name == "groups")
+ {
+ HashTable<string, bool> groups =
+ (HashTable<string, bool>) 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<string, GenericArray<string>> im_addresses =
+ (HashTable<string, GenericArray<string>>) 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<string> addresses = (GenericArray<string>) 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<string, Command>? 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<string, Individual>? 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<string, Individual> (
+ 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>? 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<string, Individual> (
+ 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>? 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<string, PersonaStore>? 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<string, PersonaStore> (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;
+ }
+}