[folks] Bug 629084 — Add a folks-import tool
- From: Travis Reitter <treitter src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [folks] Bug 629084 — Add a folks-import tool
- Date: Fri, 10 Sep 2010 18:19:59 +0000 (UTC)
commit e41c4a953bf36bd13ebabb0fb78bd1389b17ef56
Author: Philip Withnall <philip withnall collabora co uk>
Date: Wed Sep 8 19:36:04 2010 +0100
Bug 629084 â?? Add a folks-import tool
Add a folks-import tool which allows importing of Pidgin meta-contact
information to libfolks' key file. Closes: bgo#629084
Makefile.am | 1 +
configure.ac | 9 ++
folks/backend-store.vala | 2 +-
tools/Makefile.am | 34 +++++++
tools/import-pidgin.vala | 247 ++++++++++++++++++++++++++++++++++++++++++++++
tools/import.vala | 189 +++++++++++++++++++++++++++++++++++
6 files changed, 481 insertions(+), 1 deletions(-)
---
diff --git a/Makefile.am b/Makefile.am
index 5fcd882..5f0a32a 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -4,6 +4,7 @@ SUBDIRS = \
folks \
backends \
tests \
+ tools \
$(NULL)
if ENABLE_DOCS
diff --git a/configure.ac b/configure.ac
index c890fd6..ffb05ab 100644
--- a/configure.ac
+++ b/configure.ac
@@ -127,6 +127,13 @@ PKG_CHECK_MODULES([TP_GLIB_FOR_TESTS],
AM_CONDITIONAL([HAVE_TP_GLIB_FOR_TESTS], [$have_tp_glib_for_tests])
# -----------------------------------------------------------
+# Tools
+# -----------------------------------------------------------
+
+PKG_CHECK_MODULES(LIBXML, libxml-2.0, [have_libxml=yes], [have_libxml=no])
+AM_CONDITIONAL([HAVE_LIBXML], [test "$have_libxml" = "yes"])
+
+# -----------------------------------------------------------
# Documentation
# -----------------------------------------------------------
@@ -212,6 +219,7 @@ AC_CONFIG_FILES([
tests/lib/telepathy/contactlist/Makefile
tests/lib/telepathy/contactlist/session.conf
tests/tools/Makefile
+ tools/Makefile
])
AC_OUTPUT
@@ -225,6 +233,7 @@ Configure summary:
Bugreporting URL............: ${PACKAGE_BUGREPORT}
Documentation...............: ${enable_docs}
Tests.......................: ${have_tp_glib_for_tests}
+ Import tool.................: ${have_libxml}
"
diff --git a/folks/backend-store.vala b/folks/backend-store.vala
index 86d6607..b6aff32 100644
--- a/folks/backend-store.vala
+++ b/folks/backend-store.vala
@@ -172,7 +172,7 @@ public class Folks.BackendStore : Object {
if (file_type == FileType.DIRECTORY)
{
- this.load_modules_from_dir.begin (file);
+ yield this.load_modules_from_dir (file);
}
else if (mime == "application/x-sharedlib" && !is_symlink)
{
diff --git a/tools/Makefile.am b/tools/Makefile.am
new file mode 100644
index 0000000..1a0e961
--- /dev/null
+++ b/tools/Makefile.am
@@ -0,0 +1,34 @@
+if HAVE_LIBXML
+bin_PROGRAMS = folks-import
+endif
+
+VALAFLAGS = \
+ --vapidir=$(top_builddir)/folks \
+ --pkg=gee-1.0 \
+ --pkg=libxml-2.0 \
+ --pkg=folks \
+ $(NULL)
+
+folks_import_SOURCES = \
+ import.vala \
+ import-pidgin.vala \
+ $(NULL)
+folks_import_CFLAGS = \
+ $(GLIB_CFLAGS) \
+ $(GEE_CFLAGS) \
+ $(LIBXML_CFLAGS) \
+ -I$(top_srcdir)/folks \
+ $(NULL)
+folks_import_LDADD = \
+ $(GLIB_LIBS) \
+ $(GEE_LIBS) \
+ $(LIBXML_LIBS) \
+ $(top_builddir)/folks/libfolks.la \
+ $(NULL)
+
+GITIGNOREFILES = \
+ folks_import_vala.stamp \
+ $(folks_import_SOURCES:.vala=.c) \
+ $(NULL)
+
+-include $(top_srcdir)/git.mk
diff --git a/tools/import-pidgin.vala b/tools/import-pidgin.vala
new file mode 100644
index 0000000..22790a4
--- /dev/null
+++ b/tools/import-pidgin.vala
@@ -0,0 +1,247 @@
+/*
+ * 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 GLib;
+using Gee;
+using Xml;
+using Folks;
+
+public class Folks.Importers.Pidgin : Folks.Importer
+{
+ private PersonaStore destination_store;
+ private uint persona_count = 0;
+
+ public override async uint import (PersonaStore destination_store,
+ string? source_filename) throws ImportError
+ {
+ this.destination_store = destination_store;
+ string filename = source_filename;
+
+ /* Default filename */
+ if (filename == null || filename.strip () == "")
+ {
+ filename = Path.build_filename (Environment.get_home_dir (),
+ ".purple", "blist.xml", null);
+ }
+
+ Xml.Doc* xml_doc = Parser.parse_file (filename);
+
+ if (xml_doc == null)
+ {
+ throw new ImportError.MALFORMED_INPUT ("The Pidgin buddy list file " +
+ "'%s' could not be loaded.", filename);
+ }
+
+ /* Check the root node */
+ Xml.Node *root_node = xml_doc->get_root_element ();
+
+ if (root_node == null || root_node->name != "purple" ||
+ root_node->get_prop ("version") != "1.0")
+ {
+ /* Free the document manually before throwing because the garbage
+ * collector can't work on pointers. */
+ delete xml_doc;
+ throw new ImportError.MALFORMED_INPUT ("The Pidgin buddy list file " +
+ "'%s' could not be loaded: the root element could not be found " +
+ "or was not recognised.", filename);
+ }
+
+ /* Parse each <blist> child element */
+ for (Xml.Node *iter = root_node->children; iter != null;
+ iter = iter->next)
+ {
+ if (iter->type != ElementType.ELEMENT_NODE || iter->name != "blist")
+ continue;
+
+ yield this.parse_blist (iter);
+ }
+
+ /* Tidy up */
+ delete xml_doc;
+
+ stdout.printf ("Imported %u buddies from '%s'.\n", this.persona_count,
+ filename);
+
+ /* Return the number of Personas we imported */
+ return this.persona_count;
+ }
+
+ private async void parse_blist (Xml.Node *blist_node)
+ {
+ for (Xml.Node *iter = blist_node->children; iter != null;
+ iter = iter->next)
+ {
+ if (iter->type != ElementType.ELEMENT_NODE || iter->name != "group")
+ continue;
+
+ yield this.parse_group (iter);
+ }
+ }
+
+ private async void parse_group (Xml.Node *group_node)
+ {
+ string group_name = group_node->get_prop ("name");
+
+ for (Xml.Node *iter = group_node->children; iter != null;
+ iter = iter->next)
+ {
+ if (iter->type != ElementType.ELEMENT_NODE || iter->name != "contact")
+ continue;
+
+ Persona persona = yield this.parse_contact (iter);
+
+ /* Skip the persona if creating them failed or if they don't support
+ * groups. */
+ if (persona == null || !(persona is Groups))
+ continue;
+
+ try
+ {
+ Groups groupable = (Groups) persona;
+ yield groupable.change_group (group_name, true);
+ }
+ catch (GLib.Error e)
+ {
+ stderr.printf ("Error changing group of Pidgin.Persona " +
+ "'%s': %s\n", persona.iid, e.message);
+ }
+ }
+ }
+
+ private async Persona? parse_contact (Xml.Node *contact_node)
+ {
+ string alias = null;
+ HashTable<string, GenericArray<string>> im_addresses =
+ new HashTable<string, GenericArray<string>> (str_hash, str_equal);
+ string im_address_string = "";
+
+ /* Parse the <buddy> elements beneath <contact> */
+ for (Xml.Node *iter = contact_node->children; iter != null;
+ iter = iter->next)
+ {
+ if (iter->type != ElementType.ELEMENT_NODE || iter->name != "buddy")
+ continue;
+
+ string blist_protocol = iter->get_prop ("proto");
+ if (blist_protocol == null)
+ continue;
+
+ string tp_protocol =
+ this.blist_protocol_to_tp_protocol (blist_protocol);
+ if (tp_protocol == null)
+ continue;
+
+ /* Parse the <name> and <alias> elements beneath <buddy> */
+ for (Xml.Node *subiter = iter->children; subiter != null;
+ subiter = subiter->next)
+ {
+ if (subiter->type != ElementType.ELEMENT_NODE)
+ continue;
+
+ if (subiter->name == "alias")
+ alias = subiter->get_content ();
+ else if (subiter->name == "name")
+ {
+ /* The <name> element seems to give the contact ID, which
+ * we need to insert into the Persona's im-addresses property
+ * for the linking to work. */
+ string im_address = subiter->get_content ();
+
+ GenericArray<string> im_address_array =
+ im_addresses.lookup (tp_protocol);
+ if (im_address_array == null)
+ {
+ im_address_array = new GenericArray<string> ();
+ im_addresses.insert (tp_protocol, im_address_array);
+ }
+
+ im_address_array.add (im_address);
+ im_address_string += " %s\n".printf (im_address);
+ }
+ }
+ }
+
+ /* Don't bother if there's no alias and only one IM address */
+ if (im_addresses.size () < 2 &&
+ (alias == null || alias.strip () == "" ||
+ alias.strip () == im_address_string.strip ()))
+ {
+ stdout.printf ("Ignoring buddy with no alias and only one IM " +
+ "address:\n%s", im_address_string);
+ return null;
+ }
+
+ /* Create or update the relevant Persona */
+ HashTable<string, Value?> details =
+ new HashTable<string, Value?> (str_hash, str_equal);
+ Value im_addresses_value = Value (typeof (HashTable));
+ im_addresses_value.set_boxed (im_addresses);
+ details.insert ("im-addresses", im_addresses_value);
+
+ Persona persona;
+ try
+ {
+ persona =
+ yield this.destination_store.add_persona_from_details (details);
+ }
+ catch (PersonaStoreError e)
+ {
+ stderr.printf ("Failed to create new persona for buddy with alias " +
+ "'%s' and IM addresses:\n%s\nError: %s\n", alias,
+ im_address_string, e.message);
+ return null;
+ }
+
+ /* Set the Persona's details */
+ if (alias != null && persona is Alias)
+ ((Alias) persona).alias = alias;
+
+ /* Print progress */
+ stdout.printf ("Created persona '%s' for buddy with alias '%s' and IM " +
+ "addresses:\n%s", persona.uid, alias, im_address_string);
+ this.persona_count++;
+
+ return persona;
+ }
+
+ private string? blist_protocol_to_tp_protocol (string blist_protocol)
+ {
+ string tp_protocol = blist_protocol;
+ if (blist_protocol.has_prefix ("prpl-"))
+ tp_protocol = blist_protocol.substring (5);
+
+ /* Convert protocol names from Pidgin to Telepathy. Other protocol names
+ * should be OK now that we've taken off the "prpl-" prefix. See:
+ * http://telepathy.freedesktop.org/spec/Connection_Manager.html#Protocol
+ * and http://developer.pidgin.im/wiki/prpl_id. */
+ if (tp_protocol == "bonjour")
+ tp_protocol = "local-xmpp";
+ else if (tp_protocol == "novell")
+ tp_protocol = "groupwise";
+ else if (tp_protocol == "gg")
+ tp_protocol = "gadugadu";
+ else if (tp_protocol == "meanwhile")
+ tp_protocol = "sametime";
+ else if (tp_protocol == "simple")
+ tp_protocol = "sip";
+
+ return tp_protocol;
+ }
+}
diff --git a/tools/import.vala b/tools/import.vala
new file mode 100644
index 0000000..aca2a97
--- /dev/null
+++ b/tools/import.vala
@@ -0,0 +1,189 @@
+/*
+ * 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 GLib;
+using Gee;
+using Xml;
+using Folks;
+
+/*
+ * Command line application to import meta-contact information from various
+ * places into libfolks' key file backend.
+ *
+ * Used as follows:
+ * folks-import [--source=pidgin] [--source-filename=~/.purple/blist.xml]
+ */
+
+public class Folks.ImportTool : Object
+{
+ private static string source;
+ private static string source_filename;
+
+ private static const OptionEntry[] options =
+ {
+ { "source", 's', 0, OptionArg.STRING, ref ImportTool.source,
+ "Source backend name (default: 'pidgin')", "name" },
+ { "source-filename", 0, 0, OptionArg.FILENAME,
+ ref ImportTool.source_filename,
+ "Source filename (default: specific to source backend)", null },
+ { null }
+ };
+
+ public static int main (string[] args)
+ {
+ OptionContext context = new OptionContext ("â?? import meta-contact " +
+ "information to libfolks");
+ context.add_main_entries (ImportTool.options, "folks");
+
+ try
+ {
+ context.parse (ref args);
+ }
+ catch (OptionError e)
+ {
+ stderr.printf ("Couldn't parse command line options: %s\n",
+ e.message);
+ return 1;
+ }
+
+ /* We only support importing from Pidgin at the moment */
+ if (source == null || source.strip () == "")
+ source = "pidgin";
+
+ /* FIXME: We need to create this, even though we don't use it, to prevent
+ * debug message spew, as its constructor initialises the log handling.
+ * bgo#629096 */
+ IndividualAggregator aggregator = new IndividualAggregator ();
+ aggregator = null;
+
+ /* Create a main loop and start importing */
+ MainLoop main_loop = new MainLoop ();
+
+ bool success = false;
+ ImportTool.import.begin ((o, r) =>
+ {
+ success = ImportTool.import.end (r);
+ main_loop.quit ();
+ });
+
+ main_loop.run ();
+
+ return success ? 0 : 1;
+ }
+
+ private static async bool import ()
+ {
+ BackendStore backend_store = new BackendStore ();
+
+ try
+ {
+ yield backend_store.load_backends ();
+ }
+ catch (GLib.Error e1)
+ {
+ stderr.printf ("Couldn't load the backends: %s\n", e1.message);
+ return false;
+ }
+
+ /* Get the key-file backend */
+ Backend kf_backend = backend_store.get_backend_by_name ("key-file");
+
+ if (kf_backend == null)
+ {
+ stderr.printf ("Couldn't load the 'key-file' backend.\n");
+ return false;
+ }
+
+ try
+ {
+ yield kf_backend.prepare ();
+ }
+ catch (GLib.Error e2)
+ {
+ stderr.printf ("Couldn't prepare the 'key-file' backend: %s\n",
+ e2.message);
+ return false;
+ }
+
+ /* Get its only PersonaStore */
+ PersonaStore destination_store;
+ GLib.List<unowned PersonaStore> stores =
+ kf_backend.persona_stores.get_values ();
+
+ if (stores == null)
+ {
+ stderr.printf ("Couldn't load the 'key-file' backend's persona " +
+ "store.\n");
+ return false;
+ }
+
+ try
+ {
+ destination_store = stores.data;
+ yield destination_store.prepare ();
+ }
+ catch (GLib.Error e3)
+ {
+ stderr.printf ("Couldn't prepare the 'key-file' backend's persona " +
+ "store: %s\n", e3.message);
+ return false;
+ }
+
+ if (source == "pidgin")
+ {
+ Importer importer = new Importers.Pidgin ();
+
+ try
+ {
+ /* Import! */
+ yield importer.import (destination_store,
+ ImportTool.source_filename);
+ }
+ catch (ImportError e)
+ {
+ stderr.printf ("Error: %s\n", e.message);
+ return false;
+ }
+
+ /* Wait for the PersonaStore to finish writing its changes to disk */
+ yield destination_store.flush ();
+
+ return true;
+ }
+ else
+ {
+ stderr.printf ("Unrecognised source backend name '%s'. " +
+ "'pidgin' is currently the only supported source backend.\n",
+ source);
+ return false;
+ }
+ }
+}
+
+public errordomain Folks.ImportError
+{
+ MALFORMED_INPUT,
+}
+
+public abstract class Folks.Importer : Object
+{
+ public abstract async uint import (PersonaStore destination_store,
+ string? source_filename) throws ImportError;
+}
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]