[telepathy-gabble/master] search-channel.c: support dataform searches

Guillaume Desmottes guillaume.desmottes at collabora.co.uk
Wed Aug 26 09:22:03 PDT 2009


---
 src/search-channel.c |  270 ++++++++++++++++++++++++++++++++++++++++++++++----
 1 files changed, 250 insertions(+), 20 deletions(-)

diff --git a/src/search-channel.c b/src/search-channel.c
index eb1a021..878d18d 100644
--- a/src/search-channel.c
+++ b/src/search-channel.c
@@ -109,6 +109,18 @@ static const FieldNameMapping field_mappings[] = {
   { "nick",     "nickname" },
   { "email",    "email" },
   /* Fields observed in implementations of Data Forms searches */
+  /* ejabberd */
+  { "user",     "x-telepathy-identifier" },
+  { "fn",       "fn" },
+  { "middle",   "x-n-additional" },
+  { "bday",     "bday" },
+  { "ctry",     "x-adr-country" },
+  { "locality", "x-adr-locality" },
+  { "x-gender", "x-gender" },
+  { "orgname",  "x-org-name" },
+  { "orgunit",  "x-org-unit" },
+  /* openfire */
+  { "search",   "" }, /* one big search box */
   { NULL, NULL },
 };
 
@@ -231,6 +243,91 @@ parse_unextended_field_response (LmMessageNode *query_node,
   return search_keys;
 }
 
+static GPtrArray *
+parse_data_form (LmMessageNode *x_node,
+                 GError **error)
+{
+  GPtrArray *search_keys = g_ptr_array_new ();
+  gboolean found_form_type_search = FALSE;
+  NodeIter i;
+
+  if (tp_strdiff (lm_message_node_get_attribute (x_node, "type"), "form"))
+    {
+      g_set_error (error, TP_ERRORS, TP_ERROR_NOT_AVAILABLE,
+          "server is broken: <x> not type='form'");
+      goto fail;
+    }
+
+  for (i = node_iter (x_node); i; i = node_iter_next (i))
+    {
+      LmMessageNode *n = node_iter_data (i);
+      const gchar *type = lm_message_node_get_attribute (n, "type");
+      const gchar *var = lm_message_node_get_attribute (n, "var");
+      gchar *tp_name;
+
+      if (!strcmp (n->name, "title") ||
+          !strcmp (n->name, "instructions"))
+        {
+          DEBUG ("ignoring <%s>: %s", n->name, lm_message_node_get_value (n));
+          continue;
+        }
+
+      if (strcmp (n->name, "field"))
+        {
+          /* <reported> and <item> don't make sense here, and nothing else is
+           * legal.
+           */
+          DEBUG ("<%s> is not <title>, <instructions> or <field>", n->name);
+          continue;
+        }
+
+      if (!strcmp (var, "FORM_TYPE"))
+        {
+          if (node_iter (n) == NULL ||
+              strcmp (lm_message_node_get_value (node_iter_data (
+                    node_iter (n))), NS_SEARCH))
+            {
+              DEBUG ("<x> form does not have FORM_TYPE %s", NS_SEARCH);
+              g_set_error (error, TP_ERRORS, TP_ERROR_NOT_AVAILABLE,
+                  "server is broken: form lacking FORM_TYPE %s", NS_SEARCH);
+              goto fail;
+            }
+
+          found_form_type_search = TRUE;
+          continue;
+        }
+
+      /* Openfire's search plugin has one search box, called "search", and
+       * tickyboxes controlling which fields it searches.
+       *
+       * So: if the only non-tickybox is a field called "search", expose that
+       * field as "", and remember the tickyboxes. When submitting the form,
+       * tick them all (XXX: or maybe have a whitelist?)
+       */
+      if (!tp_strdiff (type, "boolean"))
+        {
+          /* TODO */
+          ;
+        }
+
+      tp_name = g_hash_table_lookup (xmpp_to_tp, var);
+      if (tp_name != NULL)
+        {
+          g_ptr_array_add (search_keys, tp_name);
+        }
+      else
+        {
+          DEBUG ("Unknown data form field: %s\n", var);
+        }
+    }
+
+  return search_keys;
+
+fail:
+  g_ptr_array_free (search_keys, TRUE);
+  return NULL;
+}
+
 static void
 parse_search_field_response (GabbleSearchChannel *chan,
                              LmMessageNode *query_node)
@@ -250,8 +347,7 @@ parse_search_field_response (GabbleSearchChannel *chan,
   else
     {
       chan->priv->xforms = TRUE;
-      e = g_error_new (TP_ERRORS, TP_ERROR_NOT_IMPLEMENTED,
-          "server uses data forms, which are not yet implemented in Gabble");
+      search_keys = parse_data_form (x_node, &e);
     }
 
   if (search_keys == NULL)
@@ -575,8 +671,63 @@ parse_result_item (GabbleSearchChannel *chan,
 }
 
 static void
-parse_search_results (GabbleSearchChannel *chan,
-                      LmMessageNode *query_node)
+parse_extended_result_item (GabbleSearchChannel *chan,
+    TpHandleRepoIface *handles,
+    LmMessageNode *item)
+{
+  GHashTable *info;
+  NodeIter i;
+
+  info = g_hash_table_new (g_str_hash, g_str_equal);
+
+  for (i = node_iter (item); i; i = node_iter_next (i))
+    {
+      LmMessageNode *field = node_iter_data (i);
+      LmMessageNode *value_node;
+      const gchar *var, *value;
+
+      if (tp_strdiff (field->name, "field"))
+        {
+          DEBUG ("found <%s/> in <item/> rather than <field/>, skipping",
+              field->name);
+          continue;
+        }
+
+      var = lm_message_node_get_attribute (field, "var");
+      if (var == NULL)
+        {
+          DEBUG ("Ignore <field/> without 'var' attribut");
+          continue;
+        }
+
+      value_node = lm_message_node_get_child (field, "value");
+      if (value_node == NULL)
+        {
+          DEBUG ("Ignore <field/> without <value/> child");
+          continue;
+        }
+
+      value = lm_message_node_get_value (value_node);
+
+      g_hash_table_insert (info, (gchar *) var, (gchar *) value);
+    }
+
+  if (g_hash_table_lookup (info, "jid") == NULL)
+    {
+      DEBUG ("<item> didn't have a jid attribute; skipping");
+    }
+  else
+    {
+      emit_search_result (chan, handles, info);
+    }
+
+  g_hash_table_destroy (info);
+}
+
+static gboolean
+parse_unextended_search_results (GabbleSearchChannel *chan,
+    LmMessageNode *query_node,
+    GError *error)
 {
   TpHandleRepoIface *handles = tp_base_connection_get_handles (
       (TpBaseConnection *) chan->base.conn, TP_HANDLE_TYPE_CONTACT);
@@ -592,6 +743,54 @@ parse_search_results (GabbleSearchChannel *chan,
         DEBUG ("found <%s/> in <query/> rather than <item/>, skipping",
             item->name);
     }
+
+  return TRUE;
+}
+
+static gboolean
+parse_extended_search_results (GabbleSearchChannel *chan,
+    LmMessageNode *query_node,
+    GError *error)
+{
+  TpHandleRepoIface *handles = tp_base_connection_get_handles (
+      (TpBaseConnection *) chan->base.conn, TP_HANDLE_TYPE_CONTACT);
+  LmMessageNode *x;
+  NodeIter i;
+
+  x = lm_message_node_get_child_with_namespace (query_node, "x", NS_X_DATA);
+  if (x == NULL)
+    {
+      error = g_error_new (TP_ERRORS, TP_ERROR_NOT_AVAILABLE,
+          "reply doens't contain a <x> node");
+      return FALSE;
+    }
+
+  for (i = node_iter (x); i; i = node_iter_next (i))
+    {
+      LmMessageNode *item = node_iter_data (i);
+
+      if (!tp_strdiff (item->name, "item"))
+        parse_extended_result_item (chan, handles, item);
+      else if (!tp_strdiff (item->name, "reported"))
+        /* TODO: check reported? */
+        ;
+      else
+        DEBUG ("found <%s/> in <query/> rather than <item/>, skipping",
+            item->name);
+    }
+
+  return TRUE;
+}
+
+static gboolean
+parse_search_results (GabbleSearchChannel *chan,
+    LmMessageNode *query_node,
+    GError *error)
+{
+  if (chan->priv->xforms)
+    return parse_extended_search_results (chan, query_node, error);
+  else
+    return parse_unextended_search_results (chan, query_node, error);
 }
 
 static LmHandlerResult
@@ -657,22 +856,21 @@ search_reply_cb (GabbleConnection *conn,
     }
 
   if (err != NULL)
-    {
-      DEBUG ("Searching failed: %s", err->message);
+    goto fail;
 
-      change_search_state (chan, GABBLE_CHANNEL_CONTACT_SEARCH_STATE_FAILED,
-          err);
+  if (!parse_search_results (chan, query_node, err))
+    goto fail;
 
-      g_error_free (err);
-    }
-  else
-    {
-      parse_search_results (chan, query_node);
+  change_search_state (chan, GABBLE_CHANNEL_CONTACT_SEARCH_STATE_COMPLETED,
+      NULL);
 
-      change_search_state (chan, GABBLE_CHANNEL_CONTACT_SEARCH_STATE_COMPLETED,
-          NULL);
-    }
+  return LM_HANDLER_RESULT_REMOVE_MESSAGE;
 
+fail:
+  DEBUG ("Searching failed: %s", err->message);
+
+  change_search_state (chan, GABBLE_CHANNEL_CONTACT_SEARCH_STATE_FAILED, err);
+  g_error_free (err);
   return LM_HANDLER_RESULT_REMOVE_MESSAGE;
 }
 
@@ -723,6 +921,41 @@ build_unextended_query (LmMessageNode *query,
     }
 }
 
+static void
+build_extended_query (LmMessageNode *query,
+    GHashTable *terms)
+{
+  LmMessageNode *x, *field;
+  GHashTableIter iter;
+  gpointer key, value;
+
+  x = lm_message_node_add_child (query, "x", "");
+  lm_message_node_set_attribute (x, "xmlns", NS_X_DATA);
+
+  /* add FORM_TYPE */
+  field = lm_message_node_add_child (x, "field", "");
+  lm_message_node_set_attributes (field,
+      "type", "hidden",
+      "var", "FORM_TYPE",
+      NULL);
+  lm_message_node_add_child (field, "value", NS_SEARCH);
+
+  /* Add search terms */
+  g_hash_table_iter_init (&iter, terms);
+
+  while (g_hash_table_iter_next (&iter, &key, &value))
+    {
+      gchar *xmpp_field = g_hash_table_lookup (tp_to_xmpp, key);
+
+      g_assert (xmpp_field != NULL);
+
+      field = lm_message_node_add_child (x, "field", "");
+      lm_message_node_set_attribute (field, "var", xmpp_field);
+      lm_message_node_add_child (field, "value", value);
+    }
+
+}
+
 static gboolean
 do_search (GabbleSearchChannel *chan,
            GHashTable *terms,
@@ -744,10 +977,7 @@ do_search (GabbleSearchChannel *chan,
 
   if (chan->priv->xforms)
     {
-      g_set_error (error, TP_ERRORS, TP_ERROR_NOT_IMPLEMENTED,
-          "server uses data forms, which are not yet implemented in Gabble");
-      lm_message_unref (msg);
-      return FALSE;
+      build_extended_query (query, terms);
     }
   else
     {
-- 
1.5.6.5




More information about the telepathy-commits mailing list