static void
infinoted_plugin_document_stream_deinitialize(gpointer plugin_info)
{
  InfinotedPluginDocumentStream* plugin;
  plugin = (InfinotedPluginDocumentStream*)plugin_info;

  while(plugin->streams != NULL)
  {
    infinoted_plugin_document_stream_close_stream(
      (InfinotedPluginDocumentStreamStream*)plugin->streams->data
    );
  }

  inf_signal_handlers_disconnect_by_func(
    G_OBJECT(infinoted_plugin_manager_get_directory(plugin->manager)),
    G_CALLBACK(infinoted_plugin_document_stream_node_removed_cb),
    plugin
  );

  if(plugin->watch != NULL)
  {
    inf_io_remove_watch(
      infinoted_plugin_manager_get_io(plugin->manager),
      plugin->watch
    );
  }

  if(plugin->socket != -1)
  {
    close(plugin->socket);
  }
}
static void
infinoted_plugin_document_stream_close_stream(
  InfinotedPluginDocumentStreamStream* stream)
{
  stream->plugin->streams = g_slist_remove(stream->plugin->streams, stream);

  if(stream->proxy != NULL || stream->subscribe_request != NULL)
    infinoted_plugin_document_stream_stop(stream, FALSE);

  if(stream->navigate_handle != NULL)
  {
    infinoted_plugin_util_navigate_cancel(stream->navigate_handle);
    stream->navigate_handle = NULL;
  }

  infinoted_plugin_document_stream_queue_finalize(&stream->send_queue);
  infinoted_plugin_document_stream_queue_finalize(&stream->recv_queue);

  inf_io_remove_watch(
    infinoted_plugin_manager_get_io(stream->plugin->manager),
    stream->watch
  );

  g_free(stream->username);
  stream->username = NULL;

  close(stream->socket);
  stream->socket = -1;

  if(stream->status == INFINOTED_PLUGIN_DOCUMENT_STREAM_NORMAL)
    g_slice_free(InfinotedPluginDocumentStreamStream, stream);
  else if(stream->status == INFINOTED_PLUGIN_DOCUMENT_STREAM_RECEIVING)
    stream->status = INFINOTED_PLUGIN_DOCUMENT_STREAM_CLOSED;
}
static void
infinoted_plugin_document_stream_add_stream(
  InfinotedPluginDocumentStream* plugin,
  InfNativeSocket new_socket)
{
  InfinotedPluginDocumentStreamStream* stream;
  stream = g_slice_new(InfinotedPluginDocumentStreamStream);

  stream->plugin = plugin;
  stream->socket = new_socket;
  stream->watch = inf_io_add_watch(
    infinoted_plugin_manager_get_io(plugin->manager),
    &stream->socket,
    INF_IO_INCOMING,
    infinoted_plugin_document_stream_io_func,
    stream,
    NULL
  );

  stream->username = NULL;

  stream->status = INFINOTED_PLUGIN_DOCUMENT_STREAM_NORMAL;
  infinoted_plugin_document_stream_queue_initialize(&stream->send_queue);
  infinoted_plugin_document_stream_queue_initialize(&stream->recv_queue);

  stream->navigate_handle = NULL;
  stream->subscribe_request = NULL;
  stream->user_request = NULL;
  stream->proxy = NULL;
  stream->user = NULL;
  stream->buffer = NULL;

  plugin->streams = g_slist_prepend(plugin->streams, stream);
}
static gboolean
infinoted_plugin_document_stream_io_out(
  InfinotedPluginDocumentStreamStream* stream,
  GError** error)
{
  GError* local_error;
  gsize sent;

  g_assert(stream->status == INFINOTED_PLUGIN_DOCUMENT_STREAM_NORMAL);
  g_assert(stream->send_queue.len > 0);

  local_error = NULL;
  sent = infinoted_plugin_document_stream_send_direct(
    stream,
    stream->send_queue.data + stream->send_queue.pos,
    stream->send_queue.len,
    &local_error
  );

  if(local_error != NULL)
  {
    g_propagate_error(error, local_error);
    infinoted_plugin_document_stream_close_stream(stream);
    return FALSE;
  }
  else if(sent == 0)
  {
    infinoted_plugin_document_stream_close_stream(stream);
    return TRUE;
  }
  else
  {
    infinoted_plugin_document_stream_queue_consume(&stream->send_queue, sent);

    if(stream->send_queue.len == 0)
    {
      inf_io_update_watch(
        infinoted_plugin_manager_get_io(stream->plugin->manager),
        stream->watch,
        INF_IO_INCOMING
      );
    }

    return TRUE;
  }
}
static void
infinoted_plugin_dbus_method_call_func(GDBusConnection* connection,
                                       const gchar* sender,
                                       const gchar* object_path,
                                       const gchar* interface_name,
                                       const gchar* method_name,
                                       GVariant* parameters,
                                       GDBusMethodInvocation* invocation,
                                       gpointer user_data)
{
  /* Dispatch to the main thread */
  InfinotedPluginDbus* plugin;
  InfinotedPluginDbusInvocation* thread_invocation;
  InfIo* io;

  plugin = (InfinotedPluginDbus*)user_data;
  thread_invocation = g_slice_new(InfinotedPluginDbusInvocation);

  thread_invocation->plugin = plugin;
  thread_invocation->ref_count = 1;
  thread_invocation->method_name = g_strdup(method_name);
  thread_invocation->parameters = g_variant_ref(parameters);
  thread_invocation->invocation = g_object_ref(invocation);
  thread_invocation->navigate = NULL;
  thread_invocation->request = NULL;
  thread_invocation->request_func = NULL;

  io = infinoted_plugin_manager_get_io(plugin->manager);

  inf_io_add_dispatch(
    io,
    infinoted_plugin_dbus_main_invocation,
    thread_invocation,
    infinoted_plugin_dbus_invocation_unref
  );
}
static gboolean
infinoted_plugin_document_stream_send(
  InfinotedPluginDocumentStreamStream* stream,
  const void* data,
  gsize len)
{
  GError* error;
  gsize sent;

  if(stream->send_queue.len > 0)
  {
    infinoted_plugin_document_stream_queue_append(
      &stream->send_queue,
      data,
      len
    );

    return TRUE;
  }
  else
  {
    error = NULL;
    sent = infinoted_plugin_document_stream_send_direct(
      stream,
      data,
      len,
      &error
    );

    if(error != NULL)
    {
      infinoted_log_warning(
        infinoted_plugin_manager_get_log(stream->plugin->manager),
        "Document stream error: %s",
        error->message
      );

      g_error_free(error);
      return FALSE;
    }
    else
    {
      if(sent < len)
      {
        infinoted_plugin_document_stream_queue_append(
          &stream->send_queue,
          (const gchar*)data + sent,
          len - sent
        );

        inf_io_update_watch(
          infinoted_plugin_manager_get_io(stream->plugin->manager),
          stream->watch,
          INF_IO_INCOMING | INF_IO_OUTGOING
        );
      }

      return TRUE;
    }
  }
}
static gboolean
infinoted_plugin_document_stream_initialize(InfinotedPluginManager* manager,
                                            gpointer plugin_info,
                                            GError** error)
{
  static const char ADDRESS_NAME[] = "org.infinote.infinoted";
  struct sockaddr_un addr;

  InfinotedPluginDocumentStream* plugin;
  plugin = (InfinotedPluginDocumentStream*)plugin_info;

  plugin->manager = manager;

  plugin->socket = socket(AF_UNIX, SOCK_STREAM, 0);
  if(plugin->socket == -1)
  {
    infinoted_plugin_document_stream_make_system_error(errno, error);
    return FALSE;
  }

  /* TODO: Make the address configurable -- note that abstract paths
   * are a Linux extension. */
  addr.sun_family = AF_UNIX;
  addr.sun_path[0] = '\0';
  memcpy(&addr.sun_path[1], ADDRESS_NAME, sizeof(ADDRESS_NAME) - 1);

  memset(
    &addr.sun_path[1] + sizeof(ADDRESS_NAME) - 1,
    '\0',
    sizeof(addr.sun_path) - 1 - (sizeof(ADDRESS_NAME) - 1)
  );

  if(!infinoted_plugin_document_stream_set_nonblock(plugin->socket, error))
    return FALSE;

  if(bind(plugin->socket, (struct sockaddr*)&addr, sizeof(addr)) == -1)
  {
    infinoted_plugin_document_stream_make_system_error(errno, error);
    return FALSE;
  }

  if(listen(plugin->socket, 5) == -1)
  {
    infinoted_plugin_document_stream_make_system_error(errno, error);
    return FALSE;
  }

  plugin->watch = inf_io_add_watch(
    infinoted_plugin_manager_get_io(plugin->manager),
    &plugin->socket,
    INF_IO_INCOMING,
    infinoted_plugin_manager_socket_accept_func,
    plugin,
    NULL
  );

  g_signal_connect(
    G_OBJECT(infinoted_plugin_manager_get_directory(plugin->manager)),
    "node-removed",
    G_CALLBACK(infinoted_plugin_document_stream_node_removed_cb),
    plugin
  );

  return TRUE;
}