/* -*- Mode: C; c-file-style: "gnu"; tab-width: 8 -*- */
/* Copyright (C) 2006 Carlos Garnacho
 *
 * This program 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 of the
 * License, or (at your option) any later version.
 *
 * This program 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 General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA.
 *
 * Authors: Carlos Garnacho Parro  <carlosg@gnome.org>
 */

#include <glib.h>
#include <glib-object.h>
#include <dbus/dbus.h>
#include <dbus/dbus-glib-lowlevel.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <signal.h>
#include "config.h"

#define DBUS_ADDRESS_ENVVAR "DBUS_SESSION_BUS_ADDRESS"
#define DBUS_INTERFACE_STB "org.freedesktop.SystemToolsBackends"
#define DBUS_INTERFACE_STB_PLATFORM "org.freedesktop.SystemToolsBackends.Platform"

/* FIXME: should be inside an object */
static GPid bus_pid = 0;
static guint watch_id = 0;
static gchar *platform = NULL;

typedef struct {
  DBusConnection *connection;
  gchar *destination;
  gint serial;
} AsyncData;

static void
async_data_free (AsyncData *data)
{
  dbus_connection_unref (data->connection);
  g_free (data->destination);
  g_free (data);
}

static void
retrieve_platform (DBusMessage *message)
{
      DBusMessageIter iter;
      const gchar *str;

      dbus_message_iter_init (message, &iter);
      dbus_message_iter_get_basic (&iter, &str);

      if (str && *str)
	platform = g_strdup (str);
}

static void
dispatch_reply (DBusPendingCall *pending_call,
		gpointer         data)
{
  DBusMessage *reply;
  AsyncData *async_data;

  reply = dbus_pending_call_steal_reply (pending_call);
  async_data = (AsyncData *) data;

  /* get the platform if necessary */
  if (dbus_message_has_interface (reply, DBUS_INTERFACE_STB_PLATFORM) &&
      dbus_message_has_member (reply, "getPlatform") && !platform)
    retrieve_platform (reply);

  /* send the reply back */
  dbus_message_set_destination (reply, async_data->destination);
  dbus_message_set_reply_serial (reply, async_data->serial);
  dbus_connection_send (async_data->connection, reply, NULL);

  dbus_message_unref (reply);
}

static gchar*
get_destination (DBusMessage *message)
{
  gchar **arr, *destination = NULL;

  if (!dbus_message_get_path_decomposed (message, &arr))
    return NULL;

  if (!arr)
    return NULL;

  /* paranoid check */
  if (arr[0] && strcmp (arr[0], "org") == 0 &&
      arr[1] && strcmp (arr[1], "freedesktop") == 0 &&
      arr[2] && strcmp (arr[2], "SystemToolsBackends") == 0 && arr[3] && !arr[4])
    destination = g_strdup_printf (DBUS_INTERFACE_STB ".%s", arr[3]);

  dbus_free_string_array (arr);

  return destination;
}

static void
dispatch_stb_message (DBusConnection *connection,
		      DBusConnection *session_connection,
		      DBusMessage    *message)
{
  DBusMessage *copy;
  DBusPendingCall *pending_call;
  AsyncData *async_data;
  gchar *destination;

  if (dbus_message_has_interface (message, DBUS_INTERFACE_STB_PLATFORM))
    {
      if (dbus_message_has_member (message, "getPlatform") && platform)
	{
	  DBusMessage *reply;
	  DBusMessageIter iter;

	  /* create a reply with the stored platform */
	  reply = dbus_message_new_method_return (message);
	  dbus_message_iter_init_append (reply, &iter);
	  dbus_message_iter_append_basic (&iter, DBUS_TYPE_STRING, &platform);

	  dbus_connection_send (connection, reply, NULL);
	  dbus_message_unref (reply);

	  return;
	}
      else if (dbus_message_has_member (message, "setPlatform"))
	retrieve_platform (message);
    }

  destination = get_destination (message);

  /* there's something wrong with the message */
  if (!destination)
    return;

  copy = dbus_message_copy (message);

  /* forward the message to the corresponding service */
  dbus_message_set_destination (copy, destination);
  dbus_connection_send_with_reply (session_connection, copy, &pending_call, -1);

  if (pending_call)
    {
      async_data = g_new0 (AsyncData, 1);
      async_data->connection = dbus_connection_ref (connection);
      async_data->destination = g_strdup (dbus_message_get_sender (message));
      async_data->serial = dbus_message_get_serial (message);

      dbus_pending_call_set_notify (pending_call, dispatch_reply, async_data, (DBusFreeFunction) async_data_free);
      dbus_pending_call_unref (pending_call);
    }

  g_free (destination);
  dbus_message_unref (copy);
}

static DBusHandlerResult
dispatcher_filter_func (DBusConnection *connection,
			DBusMessage    *message,
			void           *data)
{
  DBusConnection *session_connection = (DBusConnection *) data;

  if (dbus_message_is_signal (message, DBUS_INTERFACE_LOCAL, "Disconnected"))
    {
      /* FIXME: handle Disconnect */
    }
  else if (dbus_message_is_signal (message, DBUS_INTERFACE_DBUS, "NameOwnerChanged"))
    {
      /* FIXME: handle NameOwnerChanged */
    }
  else if (dbus_message_has_interface (message, DBUS_INTERFACE_INTROSPECTABLE) ||
	   dbus_message_has_interface (message, DBUS_INTERFACE_STB) ||
	   dbus_message_has_interface (message, DBUS_INTERFACE_STB_PLATFORM))
    dispatch_stb_message (connection, session_connection, message);

  return DBUS_HANDLER_RESULT_HANDLED;
}

static void
daemonize (void)
{
  int dev_null_fd, pidfile_fd;
  gchar *str;

  if (!getenv ("STB_NO_DAEMON"))
    {
      dev_null_fd = open ("/dev/null", O_RDWR);

      dup2 (dev_null_fd, 0);
      dup2 (dev_null_fd, 1);
      dup2 (dev_null_fd, 2);

      if (fork () != 0)
	exit (0);

      setsid ();

      if ((pidfile_fd = open (LOCALSTATEDIR "/run/system-tools-backends.pid", O_CREAT | O_WRONLY, 0600)) != -1)
	{
	  str = g_strdup_printf ("%d", getpid ());
	  write (pidfile_fd, str, strlen (str));
	  g_free (str);

	  close (pidfile_fd);
	}

      close (dev_null_fd);
    }
}

static void
on_bus_term (GPid     pid,
	     gint     status,
	     gpointer data)
{
  g_spawn_close_pid (pid);
  bus_pid = 0;

  /* if the bus dies, we screwed up */
  g_critical ("Can't live without bus.");
  g_assert_not_reached ();
}

static DBusConnection*
get_private_bus (void)
{
  DBusConnection *connection = NULL;
  DBusError error;

  dbus_error_init (&error);

  if (!bus_pid)
    {
      /* spawn private bus */
      static gchar *argv[] = { "dbus-daemon", "--session", "--print-address", "--nofork", NULL };
      gint output_fd, size;
      gchar *envvar;
      gchar str[300] = { 0, };

      if (!g_spawn_async_with_pipes (NULL, argv, NULL,
				     G_SPAWN_SEARCH_PATH | G_SPAWN_DO_NOT_REAP_CHILD,
				     NULL, NULL, &bus_pid,
				     NULL, &output_fd, NULL, NULL))
	return NULL;

      watch_id = g_child_watch_add (bus_pid, on_bus_term, NULL);
      size = read (output_fd, str, sizeof (str));
      str[size - 1] = '\0';

      envvar = g_strdup_printf (DBUS_ADDRESS_ENVVAR "=%s", str);
      putenv (envvar);

      /* get a connection with the newly created bus */
      connection = dbus_bus_get (DBUS_BUS_SESSION, &error);

      if (dbus_error_is_set (&error))
	g_critical (error.message);
    }

  return connection;
}

void
kill_private_bus (gint signal)
{
  /* terminate the private bus */
  if (bus_pid)
    {
      g_source_remove (watch_id);
      kill (bus_pid, SIGTERM);
    }

  exit (0);
}

int
main (int argc, char *argv[])
{
  DBusConnection *connection, *session_connection;
  GMainLoop *main_loop;
  DBusError error;

  /* Currently not necessary, we're not using objects */
  /* g_type_init (); */
  dbus_error_init (&error);

  daemonize ();
  signal (SIGTERM, kill_private_bus);

  session_connection = get_private_bus ();
  connection = dbus_bus_get (DBUS_BUS_SYSTEM, &error);

  if (!session_connection || !connection)
    {
      kill_private_bus (0);
      exit (-1);
    }

  dbus_connection_set_exit_on_disconnect (connection, FALSE);
  dbus_connection_set_exit_on_disconnect (session_connection, FALSE);

  dbus_connection_add_filter (connection, dispatcher_filter_func, session_connection, NULL);
  dbus_bus_request_name (connection, DBUS_INTERFACE_STB, 0, &error);

  if (dbus_error_is_set (&error))
    g_critical (error.message);


  dbus_connection_setup_with_g_main (connection, NULL);
  dbus_connection_setup_with_g_main (session_connection, NULL);

  main_loop = g_main_loop_new (NULL, FALSE);
  g_main_loop_run (main_loop);

  return 0;
}


syntax highlighted by Code2HTML, v. 0.9.1