Re: Gnome Session Services Framework



On Fri, 2005-07-08 at 11:59 -0400, John (J5) Palmieri wrote:
> On Fri, 2005-07-08 at 13:32 +0200, Rodrigo Moya wrote:
> > On Thu, 2005-07-07 at 19:05 -0400, John (J5) Palmieri wrote:
> > > 
> > > While I don't see this being included with the next GNOME 2.12 release
> > > many people have been asking for these features and I think it is time
> > > we consider fixing up the gnome-session patch to make it conditionally
> > > compilable and get people playing with it.  I think the lead time needed
> > > to fix up all the issues is not that long and distros can start to
> > > include at least the wrapper part in the near future and then eventually
> > > get it proposed for 2.14.
> > > 
> > how would we include the wrapper program without having the
> > gnome-session part of it for 2.12?
> 
> If you can get the patch ready and people are willing to accept it then
> be my guest but I don't think it is going to be ready for 2.12 (either
> part). 

ok, attached patch, with the services framework disabled by default.
-- 
Rodrigo Moya <rodrigo gnome-db org>
? compile
? depcomp
? stamp-h1
? gnome-session/gsm-dbus-utils.c
? gnome-session/gsm-dbus-utils.h
? gnome-session/gsm-service-manager.c
? gnome-session/gsm-service-manager.h
? gnome-session/gsm-service.c
? gnome-session/gsm-service.h
? gnome-session/service-manager-test
? gnome-session/session-properties.desktop.in
Index: ChangeLog
===================================================================
RCS file: /cvs/gnome/gnome-session/ChangeLog,v
retrieving revision 1.591
diff -u -p -r1.591 ChangeLog
--- ChangeLog	3 Jul 2005 18:50:14 -0000	1.591
+++ ChangeLog	8 Jul 2005 16:52:02 -0000
@@ -1,3 +1,20 @@
+2005-07-08  Rodrigo Moya <rodrigo novell com>
+
+	Original patch by Ray Strode <rstrode redhat com>
+
+	* configure.in: added --enable-services flag to compile services code
+	conditionally.
+
+	* gnome-session/Makefile.am: added DBUS_CFLAGS/LIBS and conditionally
+	compile the services-related source files.
+
+	* gnome-session/main.c: added #ifdef'ed code for new service activation
+	framework, to be compiled conditionally.
+
+	* gnome-session/gsm-dbus-utils.[ch]:
+	* gnome-session/gsm-service.[ch]:
+	* gnome-session/gsm-service-manager.[ch]: new files.
+
 2005-07-03  Aivars Kalvans  <aivars kalvans inbox lv>
 
 	* configure.in: smproxy requires glib-2.0, does not require libgnome
Index: configure.in
===================================================================
RCS file: /cvs/gnome/gnome-session/configure.in,v
retrieving revision 1.511
diff -u -p -r1.511 configure.in
--- configure.in	3 Jul 2005 18:50:14 -0000	1.511
+++ configure.in	8 Jul 2005 16:52:02 -0000
@@ -61,6 +61,22 @@ PKG_CHECK_MODULES(GNOME_SESSION, gtk+-2.
 
 PKG_CHECK_MODULES(SMPROXY, glib-2.0)
 
+dnl See if we build the services code
+enable_services=no
+build_services=no
+AC_ARG_ENABLE(services, [ --enable-services=[no/yes]	enable services code], enable_services=yes, enable_services=no)
+if test "x$enable_services" = "xyes"; then
+   PKG_CHECK_MODULES(DBUS, dbus-1 dbus-glib-1, have_dbus=yes, have_dbus=no)
+   if test "x$have_dbus" = "xyes"; then
+      build_services=yes
+      AC_SUBST(DBUS_CFLAGS)
+      AC_SUBST(DBUS_LIBS)
+      AC_DEFINE(BUILD_SERVICES, 1, [Whether to build services code or not])
+   fi
+fi
+
+AM_CONDITIONAL(BUILD_SERVICES, test "x$build_services" = "xyes")
+
 dnl gconf checks
 AC_PATH_PROG(GCONFTOOL, gconftool-2, no)
 
Index: gnome-session/Makefile.am
===================================================================
RCS file: /cvs/gnome/gnome-session/gnome-session/Makefile.am,v
retrieving revision 1.108
diff -u -p -r1.108 Makefile.am
--- gnome-session/Makefile.am	10 Jan 2005 16:36:40 -0000	1.108
+++ gnome-session/Makefile.am	8 Jul 2005 16:52:02 -0000
@@ -16,7 +16,9 @@ INCLUDES =						\
 	-DRSH_COMMAND=\""$(RSH_COMMAND)\""		\
 	-DGCONFTOOL_CMD=\""$(GCONFTOOL)\""		\
 	-DDEFAULTDIR="\"$(defaultdir)\""		\
-	-DESD_SERVER="\"$(ESD_SERVER)\""
+	-DESD_SERVER="\"$(ESD_SERVER)\""                \
+	-DDBUS_API_SUBJECT_TO_CHANGE			\
+	$(DBUS_CFLAGS)
 
 # Used by the GNOME_PROGRAM_STANDARD_PROPERTIES macros
 STANDARD_PROPERTIES_CFLAGS =                                    \
@@ -26,7 +28,8 @@ STANDARD_PROPERTIES_CFLAGS =            
 	-DDATADIR=\""$(datadir)"\"                              \
 	$(NULL)
 
-gnome_session_LDADD = $(X_LIBS) $(GNOME_SESSION_LIBS) $(LIBWRAP_LIBS)
+gnome_session_CFLAGS  = -DDEBUG
+gnome_session_LDADD = $(X_LIBS) $(GNOME_SESSION_LIBS) $(LIBWRAP_LIBS) $(DBUS_LIBS)
 gnome_session_save_LDADD = $(GNOME_SESSION_LIBS)
 gnome_session_remove_LDADD = $(GNOME_SESSION_LIBS)
 gnome_session_properties_LDADD = $(GNOME_SESSION_LIBS)
@@ -70,6 +73,18 @@ logout_test_SOURCES =		\
 	gdm-logout-action.h	\
 	$(EGGFILES)
 
+if BUILD_SERVICES
+services_source_files = 	\
+	gsm-dbus-utils.c 	\
+	gsm-dbus-utils.h	\
+	gsm-service.c		\
+	gsm-service.h		\
+	gsm-service-manager.c	\
+	gsm-service-manager.h
+else
+services_source_files =
+endif
+
 gnome_session_SOURCES =		\
 	manager.c		\
 	manager.h		\
@@ -89,6 +104,7 @@ gnome_session_SOURCES =		\
 	logout.h		\
 	splash-widget.c		\
 	splash-widget.h		\
+	$(services_source_files)\
 	gsm-xrandr.c		\
 	gsm-xrandr.h		\
 	gsm-keyring.c		\
Index: gnome-session/gsm-marshal.list
===================================================================
RCS file: /cvs/gnome/gnome-session/gnome-session/gsm-marshal.list,v
retrieving revision 1.1
diff -u -p -r1.1 gsm-marshal.list
--- gnome-session/gsm-marshal.list	27 Sep 2001 14:55:01 -0000	1.1
+++ gnome-session/gsm-marshal.list	8 Jul 2005 16:52:02 -0000
@@ -1 +1,2 @@
 NONE:INT,POINTER
+
Index: gnome-session/main.c
===================================================================
RCS file: /cvs/gnome/gnome-session/gnome-session/main.c,v
retrieving revision 1.66
diff -u -p -r1.66 main.c
--- gnome-session/main.c	21 Jun 2005 01:24:27 -0000	1.66
+++ gnome-session/main.c	8 Jul 2005 16:52:03 -0000
@@ -48,6 +48,9 @@
 #include "gsm-xrandr.h"
 #include "gsm-at-startup.h"
 #include "gsm-remote-desktop.h"
+#ifdef BUILD_SERVICES
+#include "gsm-service-manager.h"
+#endif
 
 /* Flag indicating autosave - user won't be prompted on logout to 
  * save the session */
@@ -313,15 +316,87 @@ gsm_shutdown_gconfd (void)
   g_free (command);
 }
 
+#ifdef BUILD_SERVICES
+static gboolean 
+is_done (GsmServiceManager *manager)
+{
+  return FALSE;
+#if 0
+  GSList *services, *l;
+  gboolean retval;
+
+  services = gsm_service_manager_list_services (manager);
+
+  retval = TRUE;
+  for (l = services; l != NULL; l = l->next)
+    {
+      GsmService *service;
+
+      service = (GsmService *) l->data;
+      if (gsm_service_get_status (service) != GSM_SERVICE_RUNNING)
+        {
+
+          gsm_verbose ("still waiting on service '%s'\n",
+                       gsm_service_get_name (service));
+          retval = FALSE;
+        }
+    }
+  g_slist_free (services);
+
+  return retval;
+#endif
+}
+
+
+static gboolean splashing;
+static gboolean a_t_support;
+
+static gboolean
+check_to_start_session (GsmServiceManager *manager)
+{
+  Session *the_session;
+  static guint times;
+  char *session_name_env;
+
+  if (!is_done (manager) && (times < 12))
+    {
+      times++;
+      return TRUE;
+    }
+
+  session_name_env = g_strconcat ("GNOME_DESKTOP_SESSION_ID=", session_name, NULL);
+  putenv (session_name_env);
+
+  the_session = read_session (session_name);
+
+  gsm_sound_login ();
+
+  if (splashing)
+    splash_start ();
+
+  start_session (the_session);
+
+  if (a_t_support) /* the ATs are happier if the session has started */
+    gsm_assistive_technologies_start ();
+
+  return FALSE;
+}
+#endif
+
 int
 main (int argc, char *argv[])
 {
   char *ep;
+#ifdef BUILD_SERVICES
+  GsmServiceManager *service_manager;
+  GConfClient *gconf_client;
+#else
   char *session_name_env;
   Session *the_session;
   GConfClient *gconf_client;
   gboolean splashing;
   gboolean a_t_support;
+#endif
   GError *err;
   int status;
   char *display_str;
@@ -377,8 +452,8 @@ main (int argc, char *argv[])
 		  GNOME_PARAM_POPT_TABLE, options,
 		  NULL);
 
- /* FIXME: it would be nice to skip this check if we're debugging the
- session manager. */
+  /* FIXME: it would be nice to skip this check if we're debugging the
+     session manager. */
 
   if (GNOME_CLIENT_CONNECTED (gnome_master_client ()))
   {
@@ -400,9 +475,11 @@ main (int argc, char *argv[])
 
   ignore (SIGPIPE);
 
+#ifndef BUILD_SERVICES
   /* Need DISPLAY set */
   gsm_keyring_daemon_start ();
-  
+#endif
+
   /* Read in config options */  
   gconf_client = gconf_client_get_default ();
 
@@ -450,6 +527,18 @@ main (int argc, char *argv[])
   if(failsafe)
 	session_name = FAILSAFE_SESSION;
   
+#ifdef BUILD_SERVICES
+  /* Start services that are registered with D-BUS
+   */
+  service_manager = gsm_service_manager_new ();
+
+  if (!gsm_service_manager_connect_to_bus (service_manager))
+    return 1;
+
+  gsm_service_manager_start (service_manager);
+
+  //g_timeout_add (2000, (GSourceFunc) check_to_start_session, service_manager);
+#else
   session_name_env = g_strconcat ("GNOME_DESKTOP_SESSION_ID=", session_name, NULL);
   putenv (session_name_env);
   the_session = read_session (session_name);
@@ -467,18 +556,25 @@ main (int argc, char *argv[])
 
   if (a_t_support) /* the ATs are happier if the session has started */
     gsm_assistive_technologies_start ();
+#endif
 
   gtk_main ();
 
+#ifdef BUILD_SERVICES
+  gsm_service_manager_stop (service_manager);
+  g_object_unref (service_manager);
+  
+#else
   gsm_remote_desktop_cleanup ();
 
   gsm_sound_logout ();
 
   gsm_keyring_daemon_stop ();
-  
+
   gsm_shutdown_gconfd ();
 
   clean_ice ();
+#endif
 
   return 0;
 }
/* gsm-dbus-utils.c - D-Bus related convenience functions
 *
 * Copyright (C) 2005 Ray Strode
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2, 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.  
 */
#include "gsm-dbus-utils.h"
#include <glib.h>
#include <dbus/dbus.h>
#include <dbus/dbus-glib.h>
#include <dbus/dbus-glib-lowlevel.h>

struct _GsmDBusMethodCall
{
  DBusConnection *connection;
  DBusPendingCall *pending_call;

  GsmDBusMethodCallReplyFunc reply_func;
  gpointer                   reply_func_data;
  GDestroyNotify             free_func;
};

static void
gsm_dbus_method_call_free (GsmDBusMethodCall *method_call)
{
  if (method_call->free_func != NULL)
    method_call->free_func (method_call->reply_func_data);

  g_free (method_call);
}

static void 
gsm_dbus_method_call_returned_handler (DBusPendingCall   *pending_call,
                                       GsmDBusMethodCall *method_call)
{
  DBusMessage *reply;

  g_assert (dbus_pending_call_get_completed (pending_call));

  reply = dbus_pending_call_steal_reply (pending_call);

  g_assert (reply != NULL);

  if (method_call->reply_func != NULL)
    method_call->reply_func (method_call->reply_func_data, reply);

  dbus_message_unref (reply);
  dbus_pending_call_unref (pending_call);
}

GsmDBusMethodCall * 
gsm_dbus_call_method (DBusConnection *connection,
                      const gchar    *service_name,
                      const gchar    *object_path,
                      const gchar    *interface,
                      const gchar    *method_name,
                      GsmDBusMethodCallReplyFunc reply_func,
                      gpointer                   reply_func_data,
                      GDestroyNotify             free_func,
                      gint                       timeout,
                      gint                       param_type,
                      ...)
{
  GsmDBusMethodCall *method_call;
  DBusMessage       *message;
  va_list param_list;

  message = dbus_message_new_method_call (service_name, object_path, interface,
                                          method_name);

  dbus_message_set_auto_start (message, TRUE);

  va_start (param_list, param_type);
  if (param_type != DBUS_TYPE_ARRAY)
    dbus_message_append_args_valist (message, param_type, param_list);
  /* FIXME: kludge until dbus supports string arrays again
   */
  else
    {
      gint array_type;
      gint i, length;
      DBusMessageIter iter, array_iter;
      const gchar **array;

      array_type = va_arg (param_list, gint);

      /* FIXME: support other types
       */
      g_assert (array_type == DBUS_TYPE_STRING);

      array = va_arg (param_list, const gchar **);
      length = va_arg (param_list, gint);

      dbus_message_iter_init_append (message, &iter);
      dbus_message_iter_open_container (&iter, 
                                        DBUS_TYPE_ARRAY, DBUS_TYPE_STRING_AS_STRING, 
                                        &array_iter); 

      for (i = 0; i < length; i++)
        dbus_message_iter_append_basic (&array_iter, array_type, 
                                        (gconstpointer) &array[i]);

      dbus_message_iter_close_container (&iter, &array_iter);
    }
  va_end (param_list);

  method_call = g_new (GsmDBusMethodCall, 1);
  method_call->reply_func = reply_func;
  method_call->reply_func_data = reply_func_data;
  method_call->free_func = free_func;

  dbus_connection_send_with_reply (connection, message, &method_call->pending_call, 
                                   timeout);
  dbus_pending_call_set_notify (method_call->pending_call, 
                                (DBusPendingCallNotifyFunction)
                                gsm_dbus_method_call_returned_handler, method_call,
                                (DBusFreeFunction) gsm_dbus_method_call_free);

  return method_call;
}

void 
gsm_dbus_cancel_method_call (GsmDBusMethodCall *method_call)
{
  dbus_pending_call_cancel (method_call->pending_call);
}

void 
gsm_dbus_emit_signal (DBusConnection *connection,
                      const gchar    *object_path,
                      const gchar    *interface,
                      const gchar    *signal_name,
                      gint            param_type,
                      ...)
{
  DBusMessage *message;
  va_list param_pair_list;

  message = dbus_message_new_signal (object_path, interface,
                                     signal_name);

  va_start (param_pair_list, param_type);
  dbus_message_append_args_valist (message, param_type, param_pair_list);
  va_end (param_pair_list);

  dbus_message_set_no_reply (message, TRUE);
  dbus_connection_send (connection, message, NULL);
}
/* gsm-dbus-utils.h - D-Bus related convenience functions
 *
 * Copyright (C) 2005 Ray Strode
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2, 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.  
 */
#ifndef GSM_DBUS_UTILS_H
#define GSM_DBUS_UTILS_H

#include <glib.h>
#include <dbus/dbus.h>
#include <dbus/dbus-glib.h>
#include <dbus/dbus-glib-lowlevel.h>

G_BEGIN_DECLS

typedef struct _GsmDBusMethodCall GsmDBusMethodCall;

typedef void (* GsmDBusMethodCallReplyFunc) (gpointer     user_data, 
                                             DBusMessage *reply);

GsmDBusMethodCall *gsm_dbus_call_method (DBusConnection *connection,
                                         const gchar    *service_name,
                                         const gchar    *object_path,
                                         const gchar    *interface,
                                         const gchar    *method_name,
                                         GsmDBusMethodCallReplyFunc reply_func,
                                         gpointer                   reply_func_data,
                                         GDestroyNotify             destroy_notify_func,
                                         gint                       timeout,
                                         gint                       param_type,
                                         ...);

void gsm_dbus_cancel_method_call (GsmDBusMethodCall *method_call);

void gsm_dbus_emit_signal (DBusConnection *connection,
                           const gchar    *object_path,
                           const gchar    *interface,
                           const gchar    *signal_name,
                           gint            param_type,
                           ...);

G_END_DECLS

#endif  /* GSM_DBUS_UTILS_H */
/* gsm-service.c - Services
 *
 * Copyright (C) 2005 Ray Strode, Matthias Clasen
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2, 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.  
 */
#ifdef DEBUG
#define gsm_warning(format, args...)                                           \
G_STMT_START                                                                   \
  {                                                                            \
    static const char *_function_prefix = "", *_function_suffix = "";          \
    const char *_function_name = "";                                           \
    if (G_STRFUNC && G_STRFUNC[0] != '\0')           \
      {                                                                        \
        _function_prefix = "\t";                                               \
        _function_name = G_STRFUNC;                               \
        _function_suffix = ": ";                                               \
      }                                                                        \
    g_printerr ("%.4f%s%45s%s" format,                                         \
                gsm_service_get_timestamp (),                                  \
                _function_prefix, _function_name, _function_suffix, ##args);   \
  }                                                                            \
G_STMT_END

#define gsm_verbose(format, args...)                                           \
G_STMT_START                                                                   \
  {                                                                            \
    static const char *_function_prefix = "", *_function_suffix = "";          \
    const char *_function_name = "";                                           \
    if (G_STRFUNC && G_STRFUNC[0] != '\0')           \
      {                                                                        \
        _function_prefix = "\t";                                               \
        _function_name = G_STRFUNC;                               \
        _function_suffix = ": ";                                               \
      }                                                                        \
    g_print ("%.4f%s%45s%s" format,                                            \
             gsm_service_get_timestamp (),                                     \
             _function_prefix, _function_name, _function_suffix, ##args);      \
  }                                                                            \
G_STMT_END
#else
#include <config.h>
#include "util.h"
#endif

#include "gsm-service.h"
#include "gsm-dbus-utils.h"
#include <string.h>
#include <glib/gi18n.h>
#include <dbus/dbus.h>
#include <dbus/dbus-glib.h>
#include <dbus/dbus-glib-lowlevel.h>
#include <sys/time.h>
#include <unistd.h>

struct _GsmServicePrivate 
{
  gchar *name;            /* Human-readable name */
  gchar *description;     /* Human-readable description */
  gchar *dbus_name;       /* D-BUS service name (e.g. org.gnome.Panel) */

  gchar **dependencies;   /* Dependency list of D-BUS service names */

  GsmServiceStatus status; /* STOPPED, ACTIVATING, STARTING, RUNNING, STOPPING*/

  gboolean is_disabled;   /* Is this service startable? */
};

static void gsm_service_finalize (GObject *object);
static void gsm_service_get_property (GObject *object,
                                      guint param_id,
                                      GValue *value,
                                      GParamSpec *pspec);
static void gsm_service_set_property (GObject *object,
                                      guint param_id,
                                      const GValue *value,
                                      GParamSpec *pspec);
static void
gsm_service_class_install_properties (GsmServiceClass *service_class);

static void
gsm_service_class_install_signals (GsmServiceClass *service_class);

static void
gsm_service_started_handler (GsmService *service);

static void
gsm_service_startup_complete_handler (GsmService *service);

static void
gsm_service_error_starting_handler (GsmService *service,
                                    GError     *error);

enum
{
  PROP_NAME = 1,
  PROP_DESCRIPTION,
  PROP_DBUS_NAME,
  PROP_STATUS,
  PROP_DISABLED,
};

enum
{
  STARTED = 0,
  STARTUP_COMPLETE,
  ERROR_STARTING,
  NUMBER_OF_SIGNALS
};

static const GEnumValue gsm_service_status_values[] = 
{
    { GSM_SERVICE_STATUS_STOPPED, "GSM_SERVICE_STATUS_STOPPED",
      "stopped" },
    { GSM_SERVICE_STATUS_ACTIVATING, "GSM_SERVICE_STATUS_ACTIVATING",
      "activating" },
    { GSM_SERVICE_STATUS_STARTING, "GSM_SERVICE_STATUS_STARTING",
      "starting" },
    { GSM_SERVICE_STATUS_RUNNING, "GSM_SERVICE_STATUS_RUNNING",
      "running" },
    { GSM_SERVICE_STATUS_STOPPING, "GSM_SERVICE_STATUS_STOPPING",
      "stopping" },
    { 0, NULL, NULL }
};


static guint gsm_service_signals[NUMBER_OF_SIGNALS];

G_DEFINE_TYPE (GsmService, gsm_service, G_TYPE_OBJECT);

static inline gdouble
gsm_service_get_timestamp (void)
{
  gdouble timestamp;
  GTimeVal tv = { 0, /* zero-filled */ };

  g_get_current_time (&tv);
  timestamp = (1.0 * G_USEC_PER_SEC * tv.tv_sec + tv.tv_usec) /
              (1.0 * G_USEC_PER_SEC);

  return timestamp;
}

GQuark
gsm_service_error_quark (void)
{
  static GQuark error_quark = 0;

  if (error_quark == 0)
    error_quark =
        g_quark_from_static_string ("gsm-service-error-quark");

  return error_quark;
}

static void
gsm_service_class_init (GsmServiceClass *service_class)
{
  GObjectClass *gobject_class;

  gobject_class = G_OBJECT_CLASS (service_class);

  gobject_class->get_property = gsm_service_get_property;
  gobject_class->set_property = gsm_service_set_property;
  gobject_class->finalize = gsm_service_finalize;

  gsm_service_class_install_properties (service_class);
  gsm_service_class_install_signals (service_class);

  g_type_class_add_private (gobject_class, sizeof (GsmServicePrivate));
}

GType
gsm_service_status_get_type (void)
{
  static GType type = 0;

  if (type == 0)
    type = g_enum_register_static ("GsmServiceStatus", 
                                   gsm_service_status_values);

  return type;
}

static void
gsm_service_class_install_properties (GsmServiceClass *service_class)
{
  GObjectClass *object_class;
  GParamSpec *param_spec;

  object_class = G_OBJECT_CLASS (service_class);

  param_spec = g_param_spec_string ("name", _("Name"),
                                    _("Name of Service"),
                                    NULL, G_PARAM_READABLE);
  g_object_class_install_property (object_class, PROP_NAME, param_spec);

  param_spec = g_param_spec_string ("description", _("Description"),
                                    _("Description of Service"),
                                    NULL, G_PARAM_READABLE);
  g_object_class_install_property (object_class, PROP_DESCRIPTION, param_spec);

  param_spec = g_param_spec_string ("dbus-name", _("D-BUS Name"),
                                    _("Name of D-BUS Service"),
                                    NULL, G_PARAM_READABLE);
  g_object_class_install_property (object_class, PROP_DBUS_NAME, param_spec);

  param_spec = g_param_spec_enum ("status", _("Status"), _("Current Status"),
                                  GSM_SERVICE_TYPE_STATUS,
                                  GSM_SERVICE_STATUS_STOPPED,
                                  G_PARAM_READWRITE);
  g_object_class_install_property (object_class, PROP_STATUS, param_spec);

  param_spec = g_param_spec_boolean ("disabled", _("disabled"), _("Whether"
                                                                  "service is"
                                                                  "disabled or"
                                                                  "not"),
                                  FALSE,
                                  G_PARAM_READWRITE);
  g_object_class_install_property (object_class, PROP_DISABLED, param_spec);
}

static void
gsm_service_class_install_signals (GsmServiceClass *service_class)
{
  GObjectClass *object_class;

  object_class = G_OBJECT_CLASS (service_class);

  gsm_service_signals[STARTED] =
    g_signal_new ("started",
                  G_OBJECT_CLASS_TYPE (object_class),
                  G_SIGNAL_RUN_LAST,
                  G_STRUCT_OFFSET (GsmServiceClass, started),
                  NULL, NULL, g_cclosure_marshal_VOID__VOID,
                  G_TYPE_NONE, 0);
  service_class->started = gsm_service_started_handler;
  
  gsm_service_signals[STARTUP_COMPLETE] =
    g_signal_new ("startup-complete",
                  G_OBJECT_CLASS_TYPE (object_class),
                  G_SIGNAL_RUN_LAST,
                  G_STRUCT_OFFSET (GsmServiceClass, startup_complete),
                  NULL, NULL, g_cclosure_marshal_VOID__VOID,
                  G_TYPE_NONE, 0);
  service_class->startup_complete = gsm_service_startup_complete_handler;

  gsm_service_signals[ERROR_STARTING] =
    g_signal_new ("error-starting",
                  G_OBJECT_CLASS_TYPE (object_class),
                  G_SIGNAL_RUN_LAST,
                  G_STRUCT_OFFSET (GsmServiceClass, error_starting),
                  NULL, NULL, g_cclosure_marshal_VOID__POINTER,
                  G_TYPE_NONE, 1, G_TYPE_POINTER);
  service_class->error_starting = gsm_service_error_starting_handler;
}

static void
gsm_service_init (GsmService *service)
{
  service->priv = G_TYPE_INSTANCE_GET_PRIVATE (service, 
					       GSM_TYPE_SERVICE,
					       GsmServicePrivate);
  service->priv->name = NULL;
  service->priv->description = NULL;
  service->priv->dbus_name = NULL;
  service->priv->dependencies = NULL;
  service->priv->status = GSM_SERVICE_STATUS_STOPPED;
  service->priv->is_disabled = FALSE;
}

static void
gsm_service_clear (GsmService *service)
{
  if (service->priv == NULL)
    return;

  gsm_verbose ("Freeing all resources associated with service '%s'\n",
               service->priv->dbus_name);

  g_free (service->priv->dbus_name);
  service->priv->dbus_name = NULL;
  g_free (service->priv->name);
  service->priv->name = NULL;
  g_free (service->priv->description);
  service->priv->description = NULL;

  if (service->priv->dependencies != NULL)
    g_strfreev (service->priv->dependencies);

  service->priv->dependencies = NULL;
  
  service->priv->status = GSM_SERVICE_STATUS_STOPPED;
  service->priv->is_disabled = FALSE;
}

static void 
gsm_service_finalize (GObject *object)
{
  gsm_service_clear (GSM_SERVICE (object));

  (* G_OBJECT_CLASS (gsm_service_parent_class)->finalize) (object);
}

static void
gsm_service_get_property (GObject *object,
                          guint param_id,
                          GValue *value,
                          GParamSpec *pspec)
{
  GsmService *service;

  service = GSM_SERVICE (object);

  switch (param_id)
    {
    case PROP_NAME:
      g_value_set_string (value, service->priv->name);
      break;
    case PROP_DESCRIPTION:
      g_value_set_string (value, service->priv->description);
      break;
    case PROP_DBUS_NAME:
      g_value_set_string (value, service->priv->dbus_name);
      break;
    case PROP_STATUS:
      g_value_set_enum (value, (gint) service->priv->status);
      break;
    case PROP_DISABLED:
      g_value_set_boolean (value, service->priv->is_disabled);
      break;
    default:
      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
      break;
    }
}

static void
gsm_service_set_property (GObject *object,
                          guint param_id,
                          const GValue *value,
                          GParamSpec *pspec)
{
  GsmService *service;

  service = GSM_SERVICE (object);

  switch (param_id)
    {
    case PROP_STATUS:
      gsm_service_set_status (service,
                              (GsmServiceStatus) g_value_get_enum (value));
      break;

    case PROP_DISABLED:
      gsm_service_set_disabled (service, g_value_get_boolean (value));
      break;

    default:
      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
      break;
    }
}

/**
 * gsm_service_new:
 *
 * Creates a new desktop service object.
 *
 * Returns: A new service 
 */
GsmService *
gsm_service_new (void)
{
  GsmService *service;

  service = g_object_new (GSM_TYPE_SERVICE, NULL);
  
  return service;
}

static gboolean
gsm_service_is_initialized (GsmService *service)
{
  return service->priv->name != NULL;
}

gboolean           
gsm_service_load_from_file (GsmService   *service,
                            const gchar  *filename,
                            GError      **error)
{
  GKeyFile *key_file;
  GError   *key_file_error;
  gchar    *value;

  if (gsm_service_is_initialized (service))
    gsm_service_clear (service);

  key_file = g_key_file_new ();

  key_file_error = NULL;
  if (!g_key_file_load_from_file (key_file, filename, G_KEY_FILE_NONE, 
                                  &key_file_error))
    {
      g_propagate_error (error, key_file_error);
      return FALSE;
    }

  value = g_key_file_get_string (key_file, "Desktop Service",
                                 "D-BUS-Name", &key_file_error);

  if (key_file_error != NULL)
    {
      g_free (value);
      g_propagate_error (error, key_file_error);
      return FALSE;
    }

  service->priv->dbus_name = value;

  value = g_key_file_get_locale_string (key_file, "Desktop Service",
                                        "Name", NULL, &key_file_error);

  if (key_file_error != NULL)
    {
      g_free (value);
      g_propagate_error (error, key_file_error);
      return FALSE;
    }

  service->priv->name = value;

  value = g_key_file_get_locale_string (key_file, "Desktop Service",
                                        "Description", NULL, &key_file_error);

  if (key_file_error != NULL)
    {
      g_free (value);
      g_propagate_error (error, key_file_error);
      return FALSE;
    }

  service->priv->description = value;

  if (g_key_file_has_key (key_file, "Desktop Service", "Dependencies", NULL))
    {
      gchar **dependencies;

      dependencies = g_key_file_get_string_list (key_file, "Desktop Service",
                                                 "Dependencies", NULL, &key_file_error);

      if (key_file_error != NULL)
        {
          g_strfreev (dependencies);
          g_propagate_error (error, key_file_error);
          return FALSE;
        }

      service->priv->dependencies = dependencies;
    }
  else
    service->priv->dependencies = NULL;

 if (g_key_file_has_key (key_file, "Desktop Service", "Enabled", NULL))
    {
      gboolean is_disabled;

      is_disabled = g_key_file_get_boolean (key_file, "Desktop Service",
                                           "Disabled", &key_file_error); 

      if (key_file_error != NULL)
        {
          g_propagate_error (error, key_file_error);

          service->priv->is_disabled = is_disabled;
        }
    }

  return TRUE;
}

/**
 * gsm_service_get_name: 
 * @service: a #GsmService struct
 *
 * Returns a human-readable name for @service, 
 * suitable for displaying a list of services
 * to the user. The string is taken from the
 * "Name" field of the .desktop-service file.
 *
 * Returns: a string that shouldn't be freed
 */
G_CONST_RETURN gchar *
gsm_service_get_name (GsmService *service)
{
  return service->priv->name;
}

/**
 * gsm_service_get_description: 
 * @service: a #GsmService struct
 *
 * Returns a description for @service.
 * suitable for displaying a list of services
 * to the user. The string is taken from the
 * "Description" field of the .desktop-service file.
 *
 * Returns: a string that shouldn't be freed
 */
G_CONST_RETURN gchar *
gsm_service_get_description (GsmService *service)
{
  return g_strdup (service->priv->description);
}

/**
 * gsm_service_get_dbus_name: 
 * @service: a #GsmService struct
 *
 * Returns a D-BUS service name
 *
 * Returns: a string that shouldn't be freed
 */
G_CONST_RETURN gchar *
gsm_service_get_dbus_name (GsmService *service)
{
  return service->priv->dbus_name;
}

/**
 * gsm_service_get_status: 
 * @service: a #GsmService struct
 *
 * Returns the current status of @service. 
 * The status will typically be %STATUS_RUNNING
 * or %STATUS_STOPPED, the intermediate states
 * %STATUS_STARTING and %STATUS_STOPPING should
 * only occur for short periods of time when
 * services are started or stopped.
 *
 * Returns: the current status of @service. 
 */
GsmServiceStatus  
gsm_service_get_status (GsmService *service)
{
  return service->priv->status;
}

void
gsm_service_set_status (GsmService       *service,
                        GsmServiceStatus  status)
{
  if (service->priv->status != status)
    {
      gsm_verbose ("Status set to '%s' for service '%s'\n",
                   gsm_service_status_as_string (gsm_service_get_status
                                                 (service)),
                   service->priv->dbus_name);

      service->priv->status = status;

      if (status == GSM_SERVICE_STATUS_RUNNING)
        g_signal_emit (G_OBJECT (service), gsm_service_signals[STARTUP_COMPLETE], 0);

      g_object_notify (G_OBJECT (service), "status");
    }
}

G_CONST_RETURN gchar *
gsm_service_status_as_string (GsmServiceStatus status)
{
  return gsm_service_status_values[(guint) status].value_nick;
}

/**
 * gsm_service_is_disabled:
 * @service: a #GsmService struct
 * 
 * Returns %TRUE if the service is currently
 * disabled. If a service is disabled, the 
 * service service will not start it, and if
 * it is already running, it will be stopped.
 *
 * Returns: whether @service is disabled
 */
gboolean 
gsm_service_is_disabled (GsmService *service) 
{ 
  return service->priv->is_disabled; 
}

void 
gsm_service_set_disabled (GsmService        *service,
                          gboolean           disable)
{
  /* The !! stuff is to cannonicalize input to 1 or 0 for equality check
   */
  if ((!!disable) != service->priv->is_disabled)
    {
      service->priv->is_disabled = disable;

      g_object_notify (G_OBJECT (service), "disabled");
    }
}

gchar **
gsm_service_get_dependencies (GsmService *service)
{
  guint length, i;
  gchar **dependencies;

  if (service->priv->dependencies == NULL)
    return NULL;

  length = g_strv_length (service->priv->dependencies);

  dependencies = g_new0 (char *, length + 1);

  for (i = 0; i < length; i++)
    {
      dependencies[i] = g_strdup (service->priv->dependencies[i]);
    }
  dependencies[i] = NULL;

  return dependencies;
}

static void
gsm_service_started_handler (GsmService *service)
{
  /* FIXME: Maybe track process id of service here,
   * so that service manager can watch/kill services independent 
   * of d-bus
   */
  gsm_verbose ("service '%s' is started\n",
               service->priv->dbus_name);

  gsm_service_set_status (service, GSM_SERVICE_STATUS_STARTING);
}

static void
gsm_service_startup_complete_handler (GsmService *service)
{
  gsm_verbose ("service '%s' is done starting up\n",
               service->priv->dbus_name);
}

static void
gsm_service_error_starting_handler (GsmService *service,
                                    GError     *error)
{

  if (g_error_matches (error, GSM_SERVICE_ERROR,
                       GSM_SERVICE_ERROR_CRASH_ON_STARTUP))
    {
      gsm_service_set_status (service, GSM_SERVICE_STATUS_STOPPED);
      gsm_warning ("service '%s' could not start up: it crashed\n",
                   service->priv->dbus_name);
    }
  else
    {
      /* FIXME: Not sure why this code is here.  It's probably wrong.
       */
      gsm_service_set_status (service, GSM_SERVICE_STATUS_STOPPED);

      gsm_warning ("service '%s' could not start up: %s\n",
                   service->priv->dbus_name, error->message);
    }
}

static void
gsm_service_start_by_name_handler (GsmService  *service,
                                   DBusMessage *reply)
{
  if (dbus_message_get_type (reply) == DBUS_MESSAGE_TYPE_ERROR)
    {
      gchar *message;
      GError *error;

      dbus_message_get_args (reply, NULL, DBUS_TYPE_STRING, &message,
                             DBUS_TYPE_INVALID);

      gsm_warning ("could not start service '%s': %s'\n",
                   service->priv->dbus_name, message);

      if (g_str_equal (dbus_message_get_error_name (reply),
                       "org.freedesktop.DBus.Error.Spawn.ChildExited"))
        {
          error = g_error_new (GSM_SERVICE_ERROR,
                               GSM_SERVICE_ERROR_CRASH_ON_STARTUP,
                               "%s", message);
        }
      else
        {
          error = g_error_new (GSM_SERVICE_ERROR,
                               GSM_SERVICE_ERROR_STARTING_UP,
                               "%s", message);
        }
      g_signal_emit (G_OBJECT (service), gsm_service_signals[ERROR_STARTING], 0,
                     error);
      g_error_free (error);
    }
  else
    {
      gsm_verbose ("started service '%s'\n",
                   service->priv->dbus_name);
      g_signal_emit (G_OBJECT (service), gsm_service_signals[STARTED], 0);
    }
}

void
gsm_service_start (GsmService     *service,
                   DBusConnection *connection)
{
  dbus_uint32_t   flags;

  gsm_verbose ("starting service '%s'\n", service->priv->dbus_name);

  g_return_if_fail (connection != NULL);

  gsm_service_set_status (service, GSM_SERVICE_STATUS_ACTIVATING);

  flags = 0;
  /* dbus can't handle the barrage of async calls that occurs
   * FIXME: https://bugs.freedesktop.org/show_bug.cgi?id=2813
   */
#if 1
  gsm_dbus_call_method (connection,
                        DBUS_SERVICE_DBUS,
                        DBUS_PATH_DBUS,
                        DBUS_INTERFACE_DBUS,
                        "StartServiceByName",
                        (GsmDBusMethodCallReplyFunc)
                        gsm_service_start_by_name_handler,
                        service, NULL, -1,
                        DBUS_TYPE_STRING, &service->priv->dbus_name,
                        DBUS_TYPE_UINT32, &flags, 
                        DBUS_TYPE_INVALID);
#else
  dbus_bus_start_service_by_name (connection, service->priv->dbus_name, flags, NULL, NULL);
  g_signal_emit (G_OBJECT (service), gsm_service_signals[STARTED], 0);
#endif
}

void
gsm_service_stop (GsmService     *service,
                  DBusConnection *connection)
{
  gsm_verbose ("stopping service '%s'\n", service->priv->dbus_name);

  g_return_if_fail (connection != NULL);

  gsm_service_set_status (service, GSM_SERVICE_STATUS_STOPPING);

  gsm_dbus_call_method (connection,
                        service->priv->dbus_name,
                        GSM_SERVICE_OBJECT_PATH,
                        GSM_SERVICE_INTERFACE,
                        "Stop", NULL, NULL, NULL, 300, DBUS_TYPE_INVALID);
}
/* gsm-service.h - Desktop Service Object
 *
 * Copyright (C) 2005 Matthias Clasen, Ray Strode
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2, 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.  
 */
#ifndef GSM_SERVICE_H
#define GSM_SERVICE_H

#include <glib.h>
#include <glib-object.h>
#include <dbus/dbus.h>
#include <dbus/dbus-glib.h>
#include <dbus/dbus-glib-lowlevel.h>

G_BEGIN_DECLS

#define GSM_TYPE_SERVICE            (gsm_service_get_type ())
#define GSM_SERVICE(obj)            (G_TYPE_CHECK_INSTANCE_CAST ((obj), GSM_TYPE_SERVICE, GsmService))
#define GSM_SERVICE_CLASS(klass)    (G_TYPE_CHECK_CLASS_CAST ((klass), GSM_TYPE_SERVICE, GsmServiceClass))
#define GSM_IS_SERVICE(obj)         (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GSM_TYPE_SERVICE))
#define GSM_IS_SERVICE_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GSM_TYPE_SERVICE))
#define GSM_SERVICE_GET_CLASS(obj)  (G_TYPE_INSTANCE_GET_CLASS((obj), GSM_TYPE_SERVICE, GsmServiceClass))
#define GSM_SERVICE_ERROR           (gsm_service_error_quark ())
#define GSM_SERVICE_TYPE_STATUS     (gsm_service_status_get_type ())

#define GSM_SERVICE_OBJECT_PATH "/org/gnome/ServiceManager/Service"
#define GSM_SERVICE_INTERFACE "org.gnome.ServiceManager.Service"

typedef struct _GsmService        GsmService;
typedef struct _GsmServiceClass   GsmServiceClass;
typedef struct _GsmServicePrivate GsmServicePrivate;

struct _GsmService 
{
  GObject parent;

  /*< private >*/

  GsmServicePrivate *priv;
};

struct _GsmServiceClass 
{
  GObjectClass parent_class;

  /* Signals */
  void (* started) (GsmService *service);
  void (* startup_complete) (GsmService *service);
  void (* error_starting) (GsmService *service,
                           GError     *error);
};

typedef enum 
{
  GSM_SERVICE_STATUS_STOPPED = 0,
  GSM_SERVICE_STATUS_ACTIVATING,
  GSM_SERVICE_STATUS_STARTING,
  GSM_SERVICE_STATUS_RUNNING,
  GSM_SERVICE_STATUS_STOPPING
} GsmServiceStatus;

typedef enum
{
  GSM_SERVICE_ERROR_STARTING_UP = 0,
  GSM_SERVICE_ERROR_CRASH_ON_STARTUP
} GsmServiceError;


GType              gsm_service_get_type         (void) G_GNUC_CONST;
GQuark             gsm_service_error_quark      (void) G_GNUC_CONST;
GType              gsm_service_status_get_type  (void) G_GNUC_CONST;
GsmService        *gsm_service_new              (void);
gboolean           gsm_service_load_from_file   (GsmService   *service,
                                                 const gchar  *filename,
                                                 GError      **error);
G_CONST_RETURN gchar *gsm_service_get_name         (GsmService        *service);
G_CONST_RETURN gchar *gsm_service_get_description  (GsmService        *service);
G_CONST_RETURN gchar *gsm_service_get_dbus_name    (GsmService        *service);

GsmServiceStatus   gsm_service_get_status       (GsmService        *service);
void               gsm_service_set_status       (GsmService        *service,
                                                 GsmServiceStatus   status);
G_CONST_RETURN gchar *gsm_service_status_as_string (GsmServiceStatus status);


gboolean           gsm_service_is_disabled      (GsmService        *service);
void               gsm_service_set_disabled     (GsmService        *service,
                                                 gboolean           disable);
gchar            **gsm_service_get_dependencies (GsmService        *service);

void               gsm_service_start            (GsmService        *service,
                                                 DBusConnection    *connection);
void               gsm_service_stop             (GsmService        *service,
                                                 DBusConnection    *connection);

G_END_DECLS

#endif  /* GSM_SERVICE_H */
/* gsm-service-manager.c - Manage services
 *
 * Copyright (C) 2005 Ray Strode, Matthias Clasen
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2, 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.  
 */
#define DEBUG
#ifdef DEBUG

#define gsm_warning(format, args...)                                           \
G_STMT_START                                                                   \
  {                                                                            \
    static const char *_function_prefix = "", *_function_suffix = "";          \
    const char *_function_name = "";                                           \
    if (G_STRFUNC && G_STRFUNC[0] != '\0')                                     \
      {                                                                        \
        _function_prefix = "\t";                                               \
        _function_name = G_STRFUNC;                                            \
        _function_suffix = ": ";                                               \
      }                                                                        \
    g_printerr ("%.4f%s%45s%s" format,                                         \
                gsm_service_manager_get_timestamp (),                          \
                _function_prefix, _function_name, _function_suffix, ##args);   \
  }                                                                            \
G_STMT_END

#define gsm_verbose(format, args...)                                           \
G_STMT_START                                                                   \
  {                                                                            \
    static const char *_function_prefix = "", *_function_suffix = "";          \
    const char *_function_name = "";                                           \
    if (G_STRFUNC && G_STRFUNC[0] != '\0')                                     \
      {                                                                        \
        _function_prefix = "\t";                                               \
        _function_name = G_STRFUNC;                                            \
        _function_suffix = ": ";                                               \
      }                                                                        \
    g_print ("%.4f%s%45s%s" format,                                            \
             gsm_service_manager_get_timestamp (),                             \
             _function_prefix, _function_name, _function_suffix, ##args);      \
  }                                                                            \
G_STMT_END

#else
#include <config.h>
#include "util.h"
#endif

#include "gsm-service-manager.h"
#include "gsm-service.h"
#include "gsm-dbus-utils.h"
#include <string.h>
#include <glib/gi18n.h>
#include <dbus/dbus.h>
#include <dbus/dbus-glib.h>
#include <dbus/dbus-glib-lowlevel.h>
#include <sys/time.h>

#define GSM_SERVICE_MANAGER_OBJECT_PATH "/org/gnome/ServiceManager"
#define GSM_MANAGED_SERVICE_OBJECT_PATH_PREFIX \
    "/org/gnome/ServiceManager/Services"
#define GSM_SERVICE_MANAGER_INTERFACE "org.gnome.ServiceManager"
#define GSM_MANAGED_SERVICE_INTERFACE "org.gnome.ServiceManager.ManagedService"
#define GSM_SERVICE_OBJECT_PATH \
    "/org/gnome/ServiceManager/Service"
#define GSM_SERVICE_INTERFACE \
    "org.gnome.ServiceManager.Service"

struct _GsmServiceManagerPrivate 
{
  GQueue     *services,
             *failed_services;

  GHashTable *service_dbus_name_map;
  GHashTable *service_object_map;

  DBusConnection *dbus_connection;

  gboolean dbus_message_handlers_are_registered;
};

extern char **environ;

static void gsm_service_manager_dispose (GObject *object);
static void gsm_service_manager_finalize (GObject *object);
static void gsm_service_manager_class_install_signals (GsmServiceManagerClass *service_class);

enum
{
  STARTUP_COMPLETE = 0,
  NUMBER_OF_SIGNALS
};

static guint gsm_service_manager_signals[NUMBER_OF_SIGNALS];

G_DEFINE_TYPE (GsmServiceManager, gsm_service_manager, G_TYPE_OBJECT);

static void 
gsm_service_manager_load_service_files (GsmServiceManager *manager);

static void
gsm_service_manager_service_status_changed_handler (GsmServiceManager *manager,
                                                    gpointer    useless_param,
                                                    GsmService        *service);
static DBusHandlerResult
gsm_service_manager_name_owner_changed_handler (GsmServiceManager *manager,
                                                DBusMessage       *message);

static gboolean
gsm_service_manager_start_service_and_dependencies (GsmServiceManager  *manager,
                                                    GsmService         *service,
                                                    GError            **error);
static void
gsm_service_manager_stop_service_and_dependents (GsmServiceManager  *manager,
                                                 GsmService         *service);

static inline gdouble
gsm_service_manager_get_timestamp (void)
{
  gdouble timestamp;
  GTimeVal tv = { 0, /* zero-filled */ };

  g_get_current_time (&tv);
  timestamp = (1.0 * G_USEC_PER_SEC * tv.tv_sec + tv.tv_usec) /
              (1.0 * G_USEC_PER_SEC);

  return timestamp;
}

static void
gsm_service_manager_class_init (GsmServiceManagerClass *manager_class)
{
  GObjectClass *gobject_class;

  gsm_verbose ("Initializing GsmServiceManagerClass\n");

  gobject_class = G_OBJECT_CLASS (manager_class);

  gobject_class->dispose = gsm_service_manager_dispose;
  gobject_class->finalize = gsm_service_manager_finalize;

  gsm_service_manager_class_install_signals (manager_class);

  g_type_class_add_private (manager_class, sizeof (GsmServiceManagerPrivate));
}

static void
gsm_service_manager_startup_complete_handler (GsmServiceManager *manager)
{
  gdouble timestamp;

  timestamp = gsm_service_manager_get_timestamp ();

  gsm_dbus_emit_signal (manager->priv->dbus_connection,
                        GSM_SERVICE_MANAGER_OBJECT_PATH,
                        GSM_SERVICE_MANAGER_INTERFACE,
                        "StartupComplete",
                        DBUS_TYPE_DOUBLE, &timestamp,
                        DBUS_TYPE_INVALID);
}

static void
gsm_service_manager_class_install_signals (GsmServiceManagerClass *manager_class)
{
  GObjectClass *object_class;

  object_class = G_OBJECT_CLASS (manager_class);

  gsm_service_manager_signals[STARTUP_COMPLETE] =
    g_signal_new ("startup-complete",
                  G_OBJECT_CLASS_TYPE (object_class),
                  G_SIGNAL_RUN_LAST,
                  G_STRUCT_OFFSET (GsmServiceManagerClass, startup_complete),
                  NULL, NULL, g_cclosure_marshal_VOID__VOID,
                  G_TYPE_NONE, 0);
  manager_class->startup_complete = gsm_service_manager_startup_complete_handler;
}

static void
gsm_service_manager_init (GsmServiceManager *manager)
{
  gsm_verbose ("Initializing GsmServiceManager object\n");

  manager->priv = G_TYPE_INSTANCE_GET_PRIVATE (manager, 
					       GSM_TYPE_SERVICE_MANAGER,
					       GsmServiceManagerPrivate);

  manager->priv->services = g_queue_new ();
  manager->priv->service_dbus_name_map = 
      g_hash_table_new_full (g_str_hash, 
                             g_str_equal,
                             (GDestroyNotify) g_free, 
                             (GDestroyNotify) g_object_unref);

  manager->priv->service_object_map =
      g_hash_table_new_full (g_str_hash, 
                             g_str_equal,
                             (GDestroyNotify) g_free, 
                             (GDestroyNotify) g_object_unref);

  manager->priv->failed_services = NULL;

  gsm_service_manager_load_service_files (manager);
}

static void
gsm_service_manager_remove_service (GsmServiceManager *manager,
                                    GsmService        *service);

static void 
gsm_service_manager_dispose (GObject *object)
{
  static gboolean ran_before = FALSE;
  GsmServiceManager *manager;
  GsmService *service;
  GObjectClass *gobject_class;

  if (ran_before)
    return;

  manager = GSM_SERVICE_MANAGER (object);
  gobject_class = G_OBJECT_CLASS (gsm_service_manager_parent_class);

  /* FIXME: need to refactor this code. The removal is O(n) when it could
   * be O(1) because remove_service calls g_queue_remove
   */
  while ((service = g_queue_peek_head (manager->priv->services)) != NULL)
    gsm_service_manager_remove_service (manager, service);
  g_queue_free (manager->priv->services);
  manager->priv->services = NULL;

  gsm_service_manager_disconnect_from_bus (manager);

  ran_before = TRUE;
  gobject_class->dispose (object);
}

static void 
gsm_service_manager_finalize (GObject *object)
{
  GsmServiceManager *manager;
  GObjectClass *gobject_class;
  
  manager = GSM_SERVICE_MANAGER (object);
  gobject_class = G_OBJECT_CLASS (gsm_service_manager_parent_class);

  g_hash_table_destroy (manager->priv->service_object_map);
  g_hash_table_destroy (manager->priv->service_dbus_name_map);

  gobject_class->finalize (object);
}

GQuark
gsm_service_manager_error_quark (void)
{
  static GQuark error_quark = 0;

  if (error_quark == 0)
    error_quark = g_quark_from_static_string ("gsm-service-manager-error-quark");

  return error_quark;
}

/**
 * gsm_service_manager_new:
 *
 * Creates a new service manager object. The service manager
 * manages all the services listed in the .desktop-service
 * files in $HOME/gnome2/.desktop-services and 
 * /usr/share/gnome/desktop-services, taking dependencies
 * declared in the .desktop-service files into account.
 *
 * Returns: A new service manager 
 */
GsmServiceManager *
gsm_service_manager_new (void)
{
  GObject *instance;

  instance = g_object_new (GSM_TYPE_SERVICE_MANAGER, NULL);
  
  return GSM_SERVICE_MANAGER (instance);
}
                                                                               
static gboolean
gsm_service_manager_has_service (GsmServiceManager *manager,
                                 GsmService        *service)
{
  const gchar *dbus_name;
  gboolean has_service;

  dbus_name = gsm_service_get_dbus_name (service);
  has_service = (g_hash_table_lookup (manager->priv->service_dbus_name_map, 
                                      dbus_name) != NULL);

  return has_service;
}

static void
gsm_service_manager_service_started_handler (GsmServiceManager *manager,
                                             GsmService        *service);

static void
gsm_service_manager_error_starting_service_handler (GsmServiceManager *manager,
                                                    GError          *error,
                                                    GsmService      *service);

static void
gsm_service_manager_add_service (GsmServiceManager *manager,
                                 GsmService        *service)
{
  static guint last_object_id = 0;
  gchar *object_path;

  g_hash_table_insert (manager->priv->service_dbus_name_map, 
                       g_strdup (gsm_service_get_dbus_name (service)), 
                       g_object_ref (service));

  object_path = g_strdup_printf (GSM_MANAGED_SERVICE_OBJECT_PATH_PREFIX "/%u",
                                 last_object_id);
  g_object_set_data_full (G_OBJECT (service), 
                          "gsm-service-manager-object-path", object_path, 
                          (GDestroyNotify) g_free);
  gsm_verbose ("Adding object path '%s' service object hash table\n",
               object_path);

  g_hash_table_insert (manager->priv->service_object_map, 
                       g_strdup (object_path), g_object_ref (service));
  g_queue_push_tail (manager->priv->services, 
                     g_object_ref (G_OBJECT (service)));

  g_signal_connect_swapped (G_OBJECT (service), "startup-complete",
                            G_CALLBACK (gsm_service_manager_service_started_handler),
                            manager);
  g_signal_connect_swapped (G_OBJECT (service), "error-starting", 
                            G_CALLBACK (gsm_service_manager_error_starting_service_handler),
                            manager);
  g_signal_connect_swapped (G_OBJECT (service), "notify::status",
                            G_CALLBACK (gsm_service_manager_service_status_changed_handler),
                            manager);
  last_object_id++;
}

static void
gsm_service_manager_remove_service (GsmServiceManager *manager,
                                    GsmService        *service)
{
  const gchar *object_path, *dbus_name;;

  dbus_name = gsm_service_get_dbus_name (service);
  object_path = g_object_get_data (G_OBJECT (service), 
                                   "gsm-service-manager-object-path");
  g_hash_table_remove (manager->priv->service_object_map, object_path);
  g_hash_table_remove (manager->priv->service_dbus_name_map, dbus_name);

  g_queue_remove (manager->priv->services, service);
  g_signal_handlers_disconnect_by_func (G_OBJECT (service), 
                                        G_CALLBACK (gsm_service_manager_service_status_changed_handler),
                                        manager);
  g_signal_handlers_disconnect_by_func (G_OBJECT (service), 
                                        G_CALLBACK (gsm_service_manager_error_starting_service_handler),
                                        manager);
  g_signal_handlers_disconnect_by_func (G_OBJECT (service), 
                                        G_CALLBACK (gsm_service_manager_service_started_handler),
                                        manager);
 
  g_object_unref (G_OBJECT (service));
}

static gboolean
gsm_service_manager_load_service_directory (GsmServiceManager  *manager,
                                            const gchar        *directory,
                                            GError            **error)
{
  GDir *dir;
  const gchar *name;
  gchar *path;
  GError *open_error;

  gsm_verbose ("Loading desktop service files in directory '%s'\n", directory);
               
  open_error = NULL;
  dir = g_dir_open (directory, 0, &open_error);
  if (dir == NULL)
    {
      g_propagate_error (error, open_error);
      return FALSE;
    }

  while ((name = g_dir_read_name (dir)) != NULL)
    {
      GsmService *service;

      if (g_str_has_prefix (name, "."))
        continue;

      if (!g_str_has_suffix (name, ".desktop-service"))
        continue;

      path = g_build_filename (directory, name, NULL);
      service = gsm_service_new ();

      g_object_set_data (G_OBJECT (service), "gsm-service-manager", manager);

      open_error = NULL;
      if (!gsm_service_load_from_file (service, path, &open_error))
        {
          g_object_unref (service);
          service = NULL;

          gsm_warning ("could not load service '%s': %s\n", path, 
                       open_error->message);
          g_error_free (open_error);
          open_error = NULL;
        }
      g_free (path);

      if (service == NULL)
        continue;

      if (!gsm_service_manager_has_service (manager, service))
        gsm_service_manager_add_service (manager, service);
      else
        {
          const gchar *dbus_name;

          dbus_name = gsm_service_get_dbus_name (service);
          gsm_warning ("D-BUS name '%s' found in multiple desktop service "
                       "files\n", dbus_name);
        }

      g_object_unref (service);
    }
  
  g_dir_close (dir);
  return TRUE;
}

static void 
gsm_service_manager_load_service_files (GsmServiceManager *manager)
{
  GError *load_error;
  gchar *path;
  gboolean files_loaded;

  gsm_verbose ("Loading desktop service files...\n");
  
  files_loaded = FALSE;
  path = g_build_filename (g_get_home_dir (), ".gnome2", "desktop-services",
                           NULL);
  load_error = NULL;
  gsm_service_manager_load_service_directory (manager, path, &load_error);

  if (load_error != NULL)
    {
      gsm_warning ("could not load service directory '%s': %s\n", path,
                   load_error->message);
      g_error_free (load_error);
      load_error = NULL;
    }
  else
    files_loaded = TRUE;
  g_free (path);

  gsm_service_manager_load_service_directory (manager,
                                              DEFAULTDIR "/desktop-services",
                                              &load_error);
  if (load_error != NULL)
    {
      gsm_warning ("could not load service directory "
                   "'"DEFAULTDIR"/desktop-services': %s\n",
                   load_error->message);
      g_error_free (load_error);
      load_error = NULL;
    }
  else
    files_loaded = TRUE;

  if (!files_loaded)
    gsm_warning ("no loadable services could be found\n");
}

static DBusMessage *
get_managed_service (GsmServiceManager *manager, 
                     DBusMessage       *message)
{
  gchar *dbus_service_name;
  const gchar *object_path;
  DBusMessage *reply;
  DBusError error;
  GsmService *service;

  gsm_verbose ("\n");
  dbus_error_init (&error);

  dbus_message_get_args (message, 
                         &error,
                         DBUS_TYPE_STRING, &dbus_service_name,
                         DBUS_TYPE_INVALID);

  if (dbus_error_is_set (&error))
    {
      reply = dbus_message_new_error (message, error.name,
                                      error.message);
      dbus_error_free (&error);
      return reply;
    }

  service = g_hash_table_lookup (manager->priv->service_dbus_name_map,
                                 dbus_service_name);

  if (service == NULL)
    {
      reply = dbus_message_new_error_printf (message,
                                             "org.gnome.ServiceManager.Error.ServiceNotFound",
                                             _("the service '%s' requested could"
                                               "not be found"),
                                             dbus_service_name);
      return reply;
    }

  object_path = (const gchar *) g_object_get_data (G_OBJECT (service), 
                                                   "gsm-service-manager-object-path");

  reply = dbus_message_new_method_return (message);

  dbus_message_append_args (reply,
                            DBUS_TYPE_OBJECT_PATH, &object_path,
                            DBUS_TYPE_INVALID);
  return reply;
}

static void
collect_object_paths (gpointer key, gpointer value, gpointer data)
{
  DBusMessageIter *array_iter;
  GsmService *service;
  const gchar *object_path;

  array_iter = (DBusMessageIter *) data;
  service = (GsmService *) value;
 
  object_path = (const gchar *) g_object_get_data (G_OBJECT (service), 
                                                   "gsm-service-manager-object-path");

  dbus_message_iter_append_basic (array_iter, DBUS_TYPE_OBJECT_PATH, &object_path);
}

static DBusMessage *
list_managed_services (GsmServiceManager *manager, 
                       DBusMessage       *message)
{
  DBusMessage *reply;
  DBusMessageIter iter, array_iter;

  gsm_verbose ("\n");
  reply = dbus_message_new_method_return (message);

  dbus_message_iter_init (reply, &iter);
  dbus_message_iter_open_container (&iter, 
                                    DBUS_TYPE_ARRAY, DBUS_TYPE_STRING_AS_STRING, 
                                    &array_iter); 

  g_hash_table_foreach (manager->priv->service_object_map, collect_object_paths,
                        &array_iter);

  dbus_message_iter_close_container (&iter, &array_iter);

  return reply;
}

static DBusHandlerResult
handle_service_message (GsmServiceManager *manager, 
                        GsmService        *service,
                        DBusMessage       *message)
{
  DBusMessage *reply;
  DBusHandlerResult result;

  reply = NULL;
  result = DBUS_HANDLER_RESULT_NOT_YET_HANDLED;

  if (dbus_message_is_method_call (message, GSM_MANAGED_SERVICE_INTERFACE,
                                   "GetEnvironment"))
    {
      gint i, length;
      DBusMessageIter iter, array_iter;

      reply = dbus_message_new_method_return (message);

      dbus_message_iter_init_append (reply, &iter);
      dbus_message_iter_open_container (&iter, 
                                        DBUS_TYPE_ARRAY, DBUS_TYPE_STRING_AS_STRING, 
                                        &array_iter); 

      for (length = 0; environ[length] != NULL; length++);

      for (i = 0; i < length; i++)
        dbus_message_iter_append_basic (&array_iter, DBUS_TYPE_STRING, 
                                        (gconstpointer) &environ[i]);

      dbus_message_iter_close_container (&iter, &array_iter);

        {
          const gchar *dbus_name;

          dbus_name = gsm_service_get_dbus_name (service);

          gsm_verbose ("Replying to GetEnvironment request for service '%s' "
                       "(%d elements in environment)\n", dbus_name, i);
        }

      result = DBUS_HANDLER_RESULT_HANDLED;
    }
  else if (dbus_message_is_method_call (message, GSM_MANAGED_SERVICE_INTERFACE,
                                        "Start"))
    {
      /* If a service is stopped and it is asked to be started, then start it
       * and its dependencies.
       *
       * FIXME: What should we do if it's in the STOPPING state?
       */
      if ((gsm_service_get_status (service) == GSM_SERVICE_STATUS_STOPPED) && 
          !gsm_service_is_disabled (service))
        {
          GError *error;

          error = NULL;
          /* FIXME: Send a dbus error message
           */
          if (!gsm_service_manager_start_service_and_dependencies (manager, service, 
                                                                   &error))
            {
              g_assert (error != NULL);

              gsm_warning ("could not start service '%s': %s\n",
                           gsm_service_get_dbus_name (service), error->message);
              g_error_free (error);
            }
        }

      reply = dbus_message_new_method_return (message);
      result = DBUS_HANDLER_RESULT_HANDLED;
    }
  else if (dbus_message_is_method_call (message, GSM_MANAGED_SERVICE_INTERFACE,
                                        "Stop"))
    {
      if (gsm_service_get_status (service) != GSM_SERVICE_STATUS_STOPPED)
        {
          gsm_service_manager_stop_service_and_dependents (manager, service); 
        }
      reply = dbus_message_new_method_return (message);
      result = DBUS_HANDLER_RESULT_HANDLED;
    }
  else if (dbus_message_is_method_call (message, GSM_MANAGED_SERVICE_INTERFACE,
                                        "GetDBusName"))
    {
      const gchar *dbus_name;
      reply = dbus_message_new_method_return (message);

      dbus_name = gsm_service_get_dbus_name (service);
      dbus_message_append_args (reply, 
                                DBUS_TYPE_STRING, &dbus_name,
                                DBUS_TYPE_INVALID);

      gsm_verbose ("Replying to GetDBusName request for service '%s'\n",
                   dbus_name);
      result = DBUS_HANDLER_RESULT_HANDLED;
    }
  else if (dbus_message_is_method_call (message, GSM_MANAGED_SERVICE_INTERFACE,
                                   "GetName"))
    {
      const gchar *name;
      reply = dbus_message_new_method_return (message);

      name = gsm_service_get_name (service);

      dbus_message_append_args (reply, 
                                DBUS_TYPE_STRING, &name,
                                DBUS_TYPE_INVALID);

      gsm_verbose ("Replying to GetName request for service '%s'\n",
                   name);
      result = DBUS_HANDLER_RESULT_HANDLED;
    }
  else if (dbus_message_is_method_call (message, GSM_MANAGED_SERVICE_INTERFACE,
                                   "GetDescription"))
    {
      const gchar *description;
      reply = dbus_message_new_method_return (message);

      description = gsm_service_get_description (service);

      dbus_message_append_args (reply,
                                DBUS_TYPE_STRING, &description,
                                DBUS_TYPE_INVALID);

      gsm_verbose ("Replying to GetDescription request for service '%s'\n",
                   description);
      result = DBUS_HANDLER_RESULT_HANDLED;
    }

  else if (dbus_message_is_method_call (message, GSM_MANAGED_SERVICE_INTERFACE,
                                        "SetStatus"))
    {
      const gchar *status;

      dbus_message_get_args (message, NULL,
                             DBUS_TYPE_STRING, &status,
                             DBUS_TYPE_INVALID);

      if (g_str_equal (status,  "starting"))
        gsm_service_set_status (service, GSM_SERVICE_STATUS_STARTING);
      else if (g_str_equal (status,  "running"))
        gsm_service_set_status (service, GSM_SERVICE_STATUS_RUNNING);
      else if (g_str_equal (status,  "stopping"))
        gsm_service_set_status (service, GSM_SERVICE_STATUS_STOPPING);
      else if (g_str_equal (status,  "stopped"))
        gsm_service_set_status (service, GSM_SERVICE_STATUS_STOPPED);
      else
        {
          gsm_warning ("unknown service status '%s'\n", status);
        }

      reply = dbus_message_new_method_return (message);
      result = DBUS_HANDLER_RESULT_HANDLED;
    }
  else if (dbus_message_is_method_call (message, GSM_MANAGED_SERVICE_INTERFACE,
                                        "GetStatus"))
    {
      const gchar *status;

      status = gsm_service_status_as_string (gsm_service_get_status
                                             (service));
      if (status == NULL)
        {
          gsm_warning ("unknown service status type %u\n",
                       gsm_service_get_status (service));
        }
      else
        {
          reply = dbus_message_new_method_return (message);

          dbus_message_append_args (reply,
                                    DBUS_TYPE_STRING, &status,
                                    DBUS_TYPE_INVALID);
          gsm_verbose ("Replying to GetStatus request for service '%s'\n",
                       gsm_service_get_dbus_name (service));
          result = DBUS_HANDLER_RESULT_HANDLED;
        }
    }

  if (reply != NULL)
    dbus_connection_send (manager->priv->dbus_connection, reply, NULL);

  return result;
}

static DBusHandlerResult
gsm_service_manager_dbus_message_handler (DBusConnection    *connection,
                                          DBusMessage       *message,
                                          GsmServiceManager *manager)
{
  DBusMessage *reply;
  const gchar *member;

  reply = NULL;

  gsm_verbose ("Service Manager method handler invoked\n");

  if (dbus_message_get_type (message) != DBUS_MESSAGE_TYPE_METHOD_CALL)
    return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;

  member = dbus_message_get_member (message);

  g_assert (member != NULL);

  if (g_str_equal (member, "GetManagedService"))
    reply = get_managed_service (manager, message);
  else if (g_str_equal (member, "ListManagedServices"))
    reply = list_managed_services (manager, message);

  if (reply != NULL)
    {
      dbus_connection_send (manager->priv->dbus_connection, reply, NULL);
      return DBUS_HANDLER_RESULT_HANDLED;
    }

  return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
}

static DBusHandlerResult
gsm_service_manager_managed_object_dbus_message_handler (DBusConnection    *connection,
                                                         DBusMessage       *message,
                                                         GsmServiceManager *manager)
{
  DBusMessage *reply;
  GsmService *service;
  const gchar *object_path;

  reply = NULL;

  if (dbus_message_get_type (message) != DBUS_MESSAGE_TYPE_METHOD_CALL)
    return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;

  object_path = dbus_message_get_path (message);

  g_assert (g_str_has_prefix (object_path, GSM_MANAGED_SERVICE_OBJECT_PATH_PREFIX));

  gsm_verbose ("Calling method on object '%s'\n", object_path);

  service = g_hash_table_lookup (manager->priv->service_object_map, 
                                 object_path);

  if (service != NULL)
    return handle_service_message (manager, service, message);

  gsm_warning ("Client attempted to call method on non-existant "
               "managed service '%s'\n", object_path); 

  return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
}

static void
gsm_service_manager_service_lost_name_handler (GsmServiceManager *manager,
                                               GsmService        *service)
{
  const gchar *dbus_name;

  dbus_name = gsm_service_get_dbus_name (service);
  
  gsm_verbose ("Service '%s' was disconnected from bus\n", 
               dbus_name);

  /* Service was lost--restart it if it's not disabled
   * or stopped.
   *
   * FIXME: Make sure we don't get stuck in restart/lost/restart
   * cycles.
   */
  switch (gsm_service_get_status (service))
    {
    case GSM_SERVICE_STATUS_ACTIVATING:
    case GSM_SERVICE_STATUS_STARTING:
    case GSM_SERVICE_STATUS_RUNNING:

      gsm_service_set_status (service, GSM_SERVICE_STATUS_STOPPED);

      if (!gsm_service_is_disabled (service))
        {
          GError *error;
          gsm_verbose ("Service '%s' was %s when released, "
                       "restarting\n", dbus_name,
                       (gsm_service_get_status (service) == 
                        GSM_SERVICE_STATUS_STARTING)?  "starting" : "running");

          /* FIXME: Report this problem to the user
           */
          error = NULL;
          if (!gsm_service_manager_start_service_and_dependencies (manager,
                                                                   service,
                                                                   &error))
            {
              g_assert (error != NULL);

              gsm_warning ("could not start service '%s': %s\n",
                           gsm_service_get_dbus_name (service), error->message);
              g_error_free (error);
            }
        }
      else
        gsm_verbose ("Service '%s' was already disabled when lost,"
                     "not restarting\n", dbus_name);
      break;

    case GSM_SERVICE_STATUS_STOPPING:
      gsm_verbose ("Service '%s' was stopping when released.  "
                   "Making note it has stopped.\n", dbus_name);
      gsm_service_set_status (service, GSM_SERVICE_STATUS_STOPPED);
      break;

    case GSM_SERVICE_STATUS_STOPPED:
      gsm_verbose ("Service '%s' was already stopped when released.\n",
                   dbus_name);
      break;

    default:
      gsm_warning ("Service '%s' was in unknown state %u when "
                   "released\n", dbus_name, 
                   gsm_service_get_status (service));
      break;

    }
}

static void
gsm_service_manager_service_gained_name_handler (GsmServiceManager *manager,
                                                 GsmService        *service)
{
  const gchar *dbus_name;

  dbus_name = gsm_service_get_dbus_name (service);
  
  gsm_verbose ("Service '%s' has been acquired on session bus\n", 
               dbus_name);

  /* Service was gained--make note that the service is starting now
   * if it wasn't already noted.
   */
  switch (gsm_service_get_status (service))
    {
    case GSM_SERVICE_STATUS_ACTIVATING:
      gsm_verbose ("Service '%s' was in activating state, noting it has "
                   "been activated and is now starting\n", dbus_name);
      gsm_service_set_status (service, GSM_SERVICE_STATUS_STARTING);
      break;

    case GSM_SERVICE_STATUS_STARTING:
      gsm_verbose ("Service '%s' is already in 'starting' state\n",
                   dbus_name);
      break;

    case GSM_SERVICE_STATUS_RUNNING:
      gsm_warning ("Service manager thought service '%s' was already "
                   "running, but it claims to be just starting\n",
                   dbus_name);

    case GSM_SERVICE_STATUS_STOPPING:
    case GSM_SERVICE_STATUS_STOPPED:
        {
          gsm_service_set_status (service, GSM_SERVICE_STATUS_STARTING);
        }
      break;

    default:
      gsm_warning ("Service '%s' was in unknown state %u when "
                   "acquired\n", dbus_name, 
                   gsm_service_get_status (service));
      gsm_service_set_status (service, GSM_SERVICE_STATUS_STARTING);
      break;
    }
}

static DBusHandlerResult
gsm_service_manager_name_owner_changed_handler (GsmServiceManager *manager,
                                                DBusMessage       *message)
{
  DBusError error;
  GsmService *service;
  const gchar *dbus_name, *previous_unique_name, *new_unique_name;

  dbus_error_init (&error);

  dbus_message_get_args (message, &error, 
                         DBUS_TYPE_STRING, &dbus_name,
                         DBUS_TYPE_STRING, &previous_unique_name,
                         DBUS_TYPE_STRING, &new_unique_name,
                         DBUS_TYPE_INVALID);

  if (dbus_error_is_set (&error))
    {
      gsm_warning ("could not get args from 'NameOwnerChanged' signal: %s",
                   error.message);
      dbus_error_free (&error);
      return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
    }

  g_assert (dbus_name != NULL);

  /* Short-cuts to save needless hash table lookups
   */
  if (dbus_name[0] == ':')
    return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;

  if (g_str_equal (dbus_name, "org.gnome.ServiceManager"))
    return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;

  service = g_hash_table_lookup (manager->priv->service_dbus_name_map,
                                 dbus_name);

  if (service == NULL)
    {
      gsm_verbose ("'%s' is not managed by service manager\n", 
                   dbus_name);
      return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
    }

  if ((new_unique_name == NULL) ||
      (new_unique_name[0] == '\0'))
    gsm_service_manager_service_lost_name_handler (manager, service);
  else
    gsm_service_manager_service_gained_name_handler (manager, service);

  return DBUS_HANDLER_RESULT_HANDLED;
}

static DBusHandlerResult 
gsm_service_manager_filtering_message_handler (DBusConnection    *connection, 
                                               DBusMessage       *message,
                                               GsmServiceManager *manager)
{
  if (dbus_message_is_signal (message, DBUS_INTERFACE_DBUS,
                              "NameOwnerChanged"))
    {
      if (dbus_message_has_signature (message, "sss"))
        {
          gsm_service_manager_name_owner_changed_handler (manager, message);
          return DBUS_HANDLER_RESULT_HANDLED; 
        }
      else
        gsm_warning ("NameOwnerChanged method has the wrong signature");
    }
  else if (dbus_message_get_type (message) == DBUS_MESSAGE_TYPE_METHOD_CALL)
    {
      /* FIXME: Shouldn't be necessary, but filter function seems to get
       * called sometimes when the registered objects don't
       */
      const gchar *interface, *object_path;

      interface = dbus_message_get_interface (message);
      object_path = dbus_message_get_path (message);

      if (interface != NULL)
        {
          if (g_str_equal (interface, GSM_MANAGED_SERVICE_INTERFACE))
            {
              return gsm_service_manager_dbus_message_handler (connection, 
                                                               message,
                                                               manager);
            }
          else if ((object_path != NULL) && 
                   g_str_equal (interface, GSM_MANAGED_SERVICE_INTERFACE) &&
                   g_str_has_prefix (object_path,
                                     GSM_MANAGED_SERVICE_OBJECT_PATH_PREFIX))
            {
              return gsm_service_manager_managed_object_dbus_message_handler
                                                 (connection, message, manager);
            }
        }
    }
  else if (dbus_message_get_member (message) != NULL)
    gsm_verbose ("message '%s' received and ignored by filter\n",
                 dbus_message_get_member (message));

  return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
}

static void
gsm_service_manager_register_dbus_message_handlers (GsmServiceManager *manager)
{
  DBusError error;

  static const DBusObjectPathVTable manager_vtable = 
    {
      unregister_function: (DBusObjectPathUnregisterFunction) NULL,
      message_function:    (DBusObjectPathMessageFunction)
                           gsm_service_manager_dbus_message_handler
    };

  static const DBusObjectPathVTable managed_object_vtable = 
    {
      unregister_function: (DBusObjectPathUnregisterFunction) NULL,
      message_function:    (DBusObjectPathMessageFunction)
                         gsm_service_manager_managed_object_dbus_message_handler
    };

  g_return_if_fail (!manager->priv->dbus_message_handlers_are_registered);

  dbus_error_init (&error);

  /* for NameOwnerChanged */
  dbus_bus_add_match (manager->priv->dbus_connection,
                      "sender='"DBUS_SERVICE_DBUS"',"
                      "interface='"DBUS_INTERFACE_DBUS"',"
                      "type='signal',"
                      "member='NameOwnerChanged'", &error);   

  if (dbus_error_is_set (&error))
    {
      gsm_warning ("Could not add NameOwnerChange match filter: %s\n",
                   error.message);
      dbus_error_free (&error);
    }

  dbus_bus_add_match (manager->priv->dbus_connection, "type='signal'", &error);   


  /* for replies from client library (maybe not needed?) */
  dbus_bus_add_match (manager->priv->dbus_connection,
                      "interface='"GSM_SERVICE_INTERFACE"'", &error);

  if (dbus_error_is_set (&error))
    {
      gsm_warning ("Could not add NameOwnerChange match filter: %s\n",
                   error.message);
      dbus_error_free (&error);
    }

  /* for service manager methods */
  /*
  dbus_bus_add_match (manager->priv->dbus_connection,
                      "interface='"GSM_SERVICE_MANAGER_INTERFACE"',"
                      "type='method_call'", &error);

  if (dbus_error_is_set (&error))
    {
      gsm_warning ("Could not add NameOwnerChange match filter: %s\n",
                   error.message);
      dbus_error_free (&error);
    }
    */

  /* for managed object methods */
  /*
  dbus_bus_add_match (manager->priv->dbus_connection,
                      "interface='"GSM_MANAGED_SERVICE_INTERFACE"'", &error);

  if (dbus_error_is_set (&error))
    {
      gsm_warning ("Could not add Managed Service match filter: %s\n",
                   error.message);
      dbus_error_free (&error);
    }
*/

  dbus_connection_register_object_path (manager->priv->dbus_connection,
                                        GSM_SERVICE_MANAGER_OBJECT_PATH,
                                        &manager_vtable, manager);

  dbus_connection_register_fallback (manager->priv->dbus_connection, 
                                     GSM_MANAGED_SERVICE_OBJECT_PATH_PREFIX,
                                     &managed_object_vtable, manager);

  dbus_connection_add_filter (manager->priv->dbus_connection, 
                              (DBusHandleMessageFunction)
                              gsm_service_manager_filtering_message_handler, 
                              manager, NULL);

  manager->priv->dbus_message_handlers_are_registered = TRUE;
}

static void
gsm_service_manager_unregister_dbus_message_handlers (GsmServiceManager *manager)
{
  if (!manager->priv->dbus_message_handlers_are_registered)
    return;

  dbus_connection_remove_filter (manager->priv->dbus_connection,
                                 (DBusHandleMessageFunction)
                                 gsm_service_manager_filtering_message_handler,
                                 manager);

  dbus_connection_unregister_object_path (manager->priv->dbus_connection,
                                          GSM_SERVICE_MANAGER_OBJECT_PATH);
  dbus_connection_unregister_object_path (manager->priv->dbus_connection,
                                        GSM_MANAGED_SERVICE_OBJECT_PATH_PREFIX);

  manager->priv->dbus_message_handlers_are_registered = FALSE;
}

gboolean
gsm_service_manager_connect_to_bus (GsmServiceManager *manager)
{
  DBusError error;
  DBusConnection *dbus_connection;
  
  dbus_error_init (&error);
  
  dbus_connection = dbus_bus_get (DBUS_BUS_SESSION, &error);

  if (dbus_connection == NULL)
    {
      g_assert (dbus_error_is_set (&error));
      gsm_warning ("Could not get connection to the session bus: %s\n", 
                   error.message);
      dbus_error_free (&error);

      return FALSE;
    }

  dbus_connection_set_exit_on_disconnect (dbus_connection, TRUE);

  dbus_connection_setup_with_g_main (dbus_connection, NULL);

  manager->priv->dbus_connection = dbus_connection;

  gsm_service_manager_register_dbus_message_handlers (manager);

  dbus_bus_request_name (dbus_connection, "org.gnome.ServiceManager",
                         DBUS_NAME_FLAG_PROHIBIT_REPLACEMENT, &error);

  if (dbus_error_is_set (&error))
    {
      gsm_warning ("Could not request name: %s\n", error.message);
      dbus_error_free (&error);
      return FALSE;
    }
  return TRUE;
}

void
gsm_service_manager_disconnect_from_bus (GsmServiceManager *manager)
{
  gsm_service_manager_unregister_dbus_message_handlers (manager);

  manager->priv->dbus_connection = NULL;
}


/*** Querying services ***/


/*** Starting of services ***/


static gboolean
gsm_service_manager_service_is_startable (GsmService *service)
{
  GList *dependency_list, *tmp;

  gsm_verbose ("checking if service '%s' is startable\n",
               gsm_service_get_dbus_name (service));

  if (gsm_service_is_disabled (service))
    {
      gsm_verbose ("service '%s' is not startable: service is disabled\n",
                   gsm_service_get_dbus_name (service));
      return FALSE;
    }

  dependency_list = 
      g_object_get_data (G_OBJECT (service),
                         "gsm-service-manager-service-dependency-list");

  tmp = dependency_list;
  while (tmp != NULL)
    {
      GsmService *other_dependency;

      other_dependency  = GSM_SERVICE (tmp->data);

      if (gsm_service_get_status (other_dependency) !=
          GSM_SERVICE_STATUS_RUNNING)
        {
          gsm_verbose ("service '%s' is not startable: waiting on service '%s'"
                       "\n", 
                       gsm_service_get_dbus_name (service), 
                       gsm_service_get_dbus_name (other_dependency));
          return FALSE;
        }

      tmp = tmp->next;
    }

  gsm_verbose ("service '%s' is not startable!\n", 
               gsm_service_get_dbus_name (service));

  return TRUE;      
}

static gboolean
print_failed_services (GsmServiceManager *manager)
{
  GList *services, *tmp;

  services = g_queue_peek_head_link (manager->priv->failed_services);

  g_print ("Services that have failed: [\n ");
  tmp = services;
  while (tmp != NULL)
    {
      GsmService *service;
      const gchar *name;

      service = GSM_SERVICE (tmp->data);
      name = gsm_service_get_name (service);
      g_print ("'%s' : \"%s\"\n ", name, 
               (gchar *) g_object_get_data (G_OBJECT (service),
                                            "gsm-service-reason-failed"));
      tmp = tmp->next;
    }
  g_print ("]\n");

  return TRUE;
}

static void
gsm_service_manager_service_started_handler (GsmServiceManager *manager,
                                              GsmService        *service)
{
  const gchar *name;

  name = gsm_service_get_name (service);
  gsm_verbose ("service '%s' is now running\n", name);
}

static void
gsm_service_manager_error_starting_service_handler (GsmServiceManager *manager,
                                                    GError          *error,
                                                    GsmService      *service)
{
  const gchar *name;

  name = gsm_service_get_name (service);

  g_assert (error != NULL);

  gsm_warning ("service '%s' was not started: %s\n", name, 
               error->message);

  if (manager->priv->failed_services == NULL)
    {
      manager->priv->failed_services = g_queue_new ();
      g_timeout_add (1000, (GSourceFunc) print_failed_services, manager);
    }

  g_queue_push_tail (manager->priv->failed_services, 
                     g_object_ref (G_OBJECT (service)));
  g_object_set_data (G_OBJECT (service), "gsm-service-reason-failed",
                     g_strdup (error->message));
}

static GsmService *
gsm_service_manager_lookup_service (GsmServiceManager *manager,
                                    const gchar       *dbus_name)
{
  GsmService *service;
  service = g_hash_table_lookup (manager->priv->service_dbus_name_map, 
                                 dbus_name);
  return service;
}

static void
gsm_service_manager_service_dependency_startup_complete_handler (GsmService *service,
                                                                 GsmService *dependency)
{
  gsm_verbose ("service '%s' is done starting up\n",
               gsm_service_get_dbus_name (dependency));
  if ((gsm_service_get_status (service) != GSM_SERVICE_STATUS_RUNNING) &&
      (gsm_service_get_status (service) != GSM_SERVICE_STATUS_ACTIVATING) &&
      (gsm_service_get_status (service) != GSM_SERVICE_STATUS_STARTING) &&
      gsm_service_manager_service_is_startable (service))
    {
      GsmServiceManager *manager;
      gsm_verbose ("service '%s' can now be started\n",
                   gsm_service_get_dbus_name (service));

      manager = g_object_get_data (G_OBJECT (service), "gsm-service-manager");

      gsm_service_start (service, manager->priv->dbus_connection);
    }
  else
    gsm_verbose ("cannot startup service '%s' yet\n",
                 gsm_service_get_dbus_name (service));
}

static void
gsm_service_manager_disconnect_service_from_dependencies (GsmServiceManager  *manager,
                                                          GsmService         *service)
{
  GList *dependency_list, *tmp;

  dependency_list = 
      g_object_get_data (G_OBJECT (service),
                         "gsm-service-manager-service-dependency-list");

  tmp = dependency_list;
  while (tmp != NULL)
    {
      GList *next;
      GsmService *dependency;

      dependency = GSM_SERVICE (tmp->data);

      gsm_service_manager_disconnect_service_from_dependencies (manager, 
                                                                dependency);

      g_signal_handlers_disconnect_by_func (G_OBJECT (dependency),
                                            G_CALLBACK (gsm_service_manager_service_dependency_startup_complete_handler),
                                            service);

      next = tmp->next;

      dependency_list = g_list_remove_link (dependency_list, tmp);
      g_list_free_1 (tmp);
      g_object_unref (dependency);

      tmp = next;
    }

  g_object_set_data (G_OBJECT (service),
                     "gsm-service-manager-service-dependency-list", NULL);
}

static gboolean
gsm_service_manager_start_service_and_dependencies (GsmServiceManager  *manager,
                                                    GsmService         *service,
                                                    GError            **error)
{
  gchar **dependencies; 
  const gchar *name;
  guint i;
  gboolean dependencies_started;

  if ((gsm_service_get_status (service) == GSM_SERVICE_STATUS_STARTING) ||
      (gsm_service_get_status (service) == GSM_SERVICE_STATUS_ACTIVATING) ||
      (gsm_service_get_status (service) == GSM_SERVICE_STATUS_RUNNING))
    return TRUE;

  dependencies_started = TRUE;

  name = gsm_service_get_name (service);

  gsm_verbose ("starting service '%s' and its dependencies\n", name);

  dependencies = gsm_service_get_dependencies (service);

  if (dependencies != NULL)
    {
#ifdef DEBUG
      gchar *services_to_wait_for;

      services_to_wait_for = g_strjoinv (", ", dependencies);
      gsm_verbose ("service '%s' is waiting to start on services: %s\n",
                   name, services_to_wait_for);
      g_free (services_to_wait_for);
#endif

      for (i = 0; dependencies[i] != NULL; i++)
        {
          GsmService *dependency;

          dependency = gsm_service_manager_lookup_service (manager,
                                                           dependencies[i]);

          if (dependency == NULL)
            {
              gsm_service_manager_disconnect_service_from_dependencies (manager,
                                                                        service);
              gsm_warning ("couldn't find service '%s' to startup service '%s'\n",
                           name, dependencies[i]);

              g_set_error (error, GSM_SERVICE_MANAGER_ERROR,
                           GSM_SERVICE_MANAGER_SERVICE_NOT_FOUND,
                           _("service '%s' needs service '%s' but it "
                             "cannot be found"), name, dependencies[i]);
              g_strfreev (dependencies);
              return FALSE;
            }
          else
            {
              GList *dependency_list, *dependents_list;
              GError *dependency_error;
              GsmServiceStatus status;

              dependency_error = NULL;
              status = gsm_service_get_status (dependency);
              if ((status == GSM_SERVICE_STATUS_STOPPING) ||
                  (status == GSM_SERVICE_STATUS_STOPPED))
                {
                  if (!gsm_service_manager_start_service_and_dependencies (manager,
                                                                           dependency,
                                                                           &dependency_error))
                    {
                      gsm_service_manager_disconnect_service_from_dependencies (manager,
                                                                                service);
                      g_propagate_error (error, dependency_error);
                      g_strfreev (dependencies);
                      return FALSE;
                    }

                }
              
              dependency_list = 
                  g_object_get_data (G_OBJECT (service),
                                 "gsm-service-manager-service-dependency-list");

              if (!g_list_find (dependency_list, dependency))
                {
                  dependency_list = g_list_prepend (dependency_list, 
                                                    g_object_ref (dependency));

                  g_object_set_data (G_OBJECT (service),
                                     "gsm-service-manager-service-dependency-list",
                                     dependency_list);
                }

              dependents_list =
                  g_object_get_data (G_OBJECT (dependency),
                                 "gsm-service-manager-service-dependents-list");

              if (!g_list_find (dependents_list, service))
                {
                  dependents_list = g_list_prepend (dependents_list,
                                                    g_object_ref (service));

                  g_object_set_data (G_OBJECT (dependency),
                                     "gsm-service-manager-service-dependents-list",
                                     dependents_list);
                }

              if (status != GSM_SERVICE_STATUS_RUNNING)
                {
                  g_signal_connect_swapped (G_OBJECT (dependency),
                                            "startup-complete",
                                            G_CALLBACK (gsm_service_manager_service_dependency_startup_complete_handler),
                                            service);
                  dependencies_started = FALSE;
                }
            }
        }
    }

  if (dependencies_started)
    {
      gsm_verbose ("service '%s' has no unstarted dependencies, "
                   "starting immediately\n", 
                   name);

      gsm_service_start (service, 
                         manager->priv->dbus_connection);
    }

  g_strfreev (dependencies);
  return TRUE;
}

static void
gsm_service_manager_stop_service_and_dependents (GsmServiceManager  *manager,
                                                 GsmService         *service)
{
  GList *dependent_list, *tmp;

  gsm_verbose ("stopping service '%s' and other services that depend on it\n", 
               gsm_service_get_name (service));

  gsm_service_manager_disconnect_service_from_dependencies (manager,
                                                            service);

  dependent_list = 
      g_object_get_data (G_OBJECT (service),
                         "gsm-service-manager-service-dependents-list");

  tmp = dependent_list;
  while (tmp != NULL)
    {
      GsmService *dependent;

      dependent = GSM_SERVICE (tmp->data);

      gsm_service_manager_stop_service_and_dependents (manager, dependent);

      g_object_unref (G_OBJECT (dependent));
      tmp = tmp->next;
    }

  g_list_free (dependent_list);
  g_object_set_data (G_OBJECT (service),
                     "gsm-service-manager-service-dependents-list",
                     NULL);

  gsm_service_stop (service, manager->priv->dbus_connection);
}

static void
gsm_service_manager_start_services_and_dependencies (GsmServiceManager *manager)
{
  GList *tmp;

  gsm_verbose ("starting services and dependencies\n");

  tmp = g_queue_peek_head_link (manager->priv->services);
  while (tmp)
    {
      GError *error;
      GsmService *service;

      service = GSM_SERVICE (tmp->data);
      error = NULL;
      if (!gsm_service_manager_start_service_and_dependencies (manager, service, 
                                                                &error))
        {
          gsm_warning ("could not start all the dependencies of service '%s'%s%s\n",
                       gsm_service_get_name (service),
                       error != NULL? ": " : "", error->message);

          if (error != NULL)
            g_error_free (error);
        }
      tmp = tmp->next;
    }
}

static void
gsm_service_manager_stop_services_and_dependents (GsmServiceManager *manager)
{
  GList *tmp;

  gsm_verbose ("stopping services and dependendents\n");

  tmp = g_queue_peek_head_link (manager->priv->services);
  while (tmp)
    {
      GsmService *service;

      service = GSM_SERVICE (tmp->data);

      gsm_service_manager_stop_service_and_dependents (manager, service);

      tmp = tmp->next;
    }
}

/**
 * gsm_service_manager_start:
 * @manager: a #GsmServiceManager
 * 
 * Starts all the services managed by @manager, taking 
 * dependencies between services into account. Services
 * are not started if they have been disabled. The 
 * starting is done asynchronously, so this function
 * will return immediately.
 */
void 
gsm_service_manager_start (GsmServiceManager *manager)
{
  if (g_queue_is_empty (manager->priv->services))
    {
      gsm_verbose ("No services to start\n");
      return;
    }

  gsm_service_manager_start_services_and_dependencies (manager);
}

/**
 * gsm_service_manager_stop:
 * @manager: a #GsmServiceManager
 * 
 * Stops all the services managed by @manager, taking 
 * dependencies declared in the .desktop-service files into account. 
 * The stopping is done asynchronously, so this function
 * will return immediately.
 */
void 
gsm_service_manager_stop (GsmServiceManager *manager)
{
  gsm_service_manager_stop_services_and_dependents (manager);
}

static void
gsm_service_manager_service_status_changed_handler (GsmServiceManager *manager,
                                                    gpointer     useless_param,
                                                    GsmService        *service)
{
  gdouble timestamp;
  const gchar *signal;
  const gchar *name, *dbus_name;

  name = gsm_service_get_name (service);

  timestamp = gsm_service_manager_get_timestamp ();

  switch (gsm_service_get_status (service))
    {
    case GSM_SERVICE_STATUS_ACTIVATING:
      gsm_verbose ("Service '%s' changed status to 'starting'\n",
                   name); 
      signal = "ServiceActivating";
      break;
    case GSM_SERVICE_STATUS_STARTING:
      gsm_verbose ("Service '%s' changed status to 'starting'\n",
                   name); 
      signal = "ServiceStarting";
      break;
    case GSM_SERVICE_STATUS_RUNNING:
      gsm_verbose ("Service '%s' changed status to 'running'\n",
                   name); 
      signal = "ServiceStarted";
      break;
    case GSM_SERVICE_STATUS_STOPPING:
      gsm_verbose ("Service '%s' changed status to 'stopping'\n",
                   name); 
      signal = "ServiceStopping";
      break;
    case GSM_SERVICE_STATUS_STOPPED:
      gsm_verbose ("Service '%s' changed status to 'stopped'\n",
                   name); 
      signal = "ServiceStopped";
      break;
    default:
      gsm_warning ("Status '%u' is unknown!",
                   gsm_service_get_status (service));
      signal = NULL;
      break;
    }

  dbus_name = gsm_service_get_dbus_name (service);

  if (signal != NULL)
    {
      gsm_verbose ("emitting signal '"GSM_SERVICE_MANAGER_INTERFACE".%s' "
                   "from object '"GSM_SERVICE_MANAGER_OBJECT_PATH"'\n", signal);

      gsm_dbus_emit_signal (manager->priv->dbus_connection,
                            GSM_SERVICE_MANAGER_OBJECT_PATH,
                            GSM_SERVICE_MANAGER_INTERFACE,
                            signal,
                            DBUS_TYPE_STRING, &name,
                            DBUS_TYPE_STRING, &dbus_name,
                            DBUS_TYPE_DOUBLE, &timestamp,
                            DBUS_TYPE_INVALID);
    }
}

/*** Debugging and testing ***/


#if 0 && defined (DEBUG)

static void
dump_cb (gpointer key, 
	 gpointer value,
	 gpointer data)
{
  GsmService *service = value;
  const gchar *name, *dbus_name;
  gchar **dependencies;
  
  name = gsm_service_get_name (service);
  dbus_name = gsm_service_get_dbus_name (service);
  g_print ("service %s (%s), status '%s' %s\n", name, dbus_name, 
	   gsm_service_status_as_string (gsm_service_get_status (service)),
	   gsm_service_is_disabled (service) ? ", disabled" : "");
  dependencies = gsm_service_get_dependencies (service);
  if (dependencies)
    {
      gsize i;

      g_print ("\tdepends on");
      for (i = 0; dependencies[i]; i++)
	g_print ("%s %s", i > 0 ? "," : "", dependencies[i]);
      g_print ("\n");
    }
  g_strfreev (dependencies);
}

static void 
dump_services (GsmServiceManager *manager,
	       gint               step,
	       const gchar       *title)
{
  g_print ("\nStep %d (%s)^\n================================\n\n", step, title);
  g_hash_table_foreach (manager->priv->service_dbus_name_map, dump_cb, NULL);
}

static gboolean is_started_up = FALSE;

static void
startup_complete (void)
{
  gsm_verbose ("Startup is now complete\n");
  is_started_up = TRUE;
}

static gboolean 
test_timeout (gpointer data)
{
  static gint step = 0;
  static gboolean run_again;
  GMainLoop *loop = data;
  static GsmServiceManager *manager = NULL;

  run_again = TRUE;
  switch (step)
    {
    case 0:
      manager = gsm_service_manager_new ();
      g_signal_connect (G_OBJECT (manager), "startup-complete",
                        startup_complete, NULL);
      break;
    case 1:
      dump_services (manager, step / 2, "new");
      break;
    case 2:
      run_again = gsm_service_manager_connect_to_bus (manager);
      break;
    case 3:
      dump_services (manager, step / 2, "connect to bus");
      break;
    case 4:
      gsm_service_manager_start (manager);
      break;
    case 5:
      dump_services (manager, step / 2, "start");  
      if (!is_started_up)
        step--;
      break;
#if 0
    case 6:
      gsm_service_manager_stop (manager);
      break;
    case 7:
      dump_services (manager, step / 2, "stop");  
      break;
    case 8:
      {
        GsmServiceBatch *batch;
	GsmService *service;

	service = g_hash_table_lookup (manager->priv->service_dbus_name_map, "org.gnome.Panel");

        if (service == NULL)
          {
            gsm_warning ("service 'org.gnome.Panel' is not loaded");
            run_again = FALSE;
          }
        else
          {
            batch = gsm_service_batch_new_from_service (manager, service);
            gsm_service_batch_set_operation (batch, 
                                             GSM_SERVICE_BATCH_OPERATION_START);
            gsm_service_manager_add_batch (manager, batch);
            gsm_service_manager_queue_batch (manager);
          }
      }
      break;
    case 9:
      dump_services (manager, step / 2, "start org.gnome.Panel");  
      break;
    case 10:
      {
        GsmServiceBatch *batch;
	GsmService *service;

	service = g_hash_table_lookup (manager->priv->service_dbus_name_map, "org.gnome.Nautilus");

        if (service == NULL)
          {
            gsm_warning ("service 'org.gnome.Nautilus' is not loaded");
            run_again = FALSE;
          }
        else
          {
            batch = gsm_service_batch_new_from_service (manager, service);
            gsm_service_batch_set_operation (batch, 
                                             GSM_SERVICE_BATCH_OPERATION_STOP);
            gsm_service_manager_add_batch (manager, batch);
            gsm_service_manager_queue_batch (manager);
          }
      }
      break;
    case 11:
      dump_services (manager, step / 2, "stop org.gnome.Nautilus");  
      break;
    case 12:
      {
        GsmServiceBatch *batch;
	GsmService *service;

	service = g_hash_table_lookup (manager->priv->service_dbus_name_map, "org.gnome.Keyring");
        
        if (service == NULL)
          {
            gsm_warning ("service 'org.gnome.Keyring' is not loaded");
            run_again = FALSE;
          }
        else
          {
            gsm_service_set_disabled (service, TRUE);
            batch = gsm_service_batch_new_from_service (manager, service);
            gsm_service_batch_set_operation (batch, 
                                             GSM_SERVICE_BATCH_OPERATION_STOP);
            gsm_service_manager_add_batch (manager, batch);
            gsm_service_manager_queue_batch (manager);
          }
      }
      break;
    case 13:
      dump_services (manager, step / 2, "disable org.gnome.Keyring");  
      break;
    case 14:
      gsm_service_manager_start (manager);
      break;
    case 15:
      dump_services (manager, step / 2, "start");  
      break;
    case 16:
      {
        GsmServiceBatch *batch;
	GsmService *service;

	service = g_hash_table_lookup (manager->priv->service_dbus_name_map, "org.gnome.Keyring");
        
        if (service == NULL)
          {
            gsm_warning ("service 'org.gnome.Keyring' is not loaded");
            run_again = FALSE;
          }
        else
          {
            gsm_service_set_disabled (service, FALSE);
            batch = gsm_service_batch_new_from_service (manager, service);
            gsm_service_batch_set_operation (batch, 
                                             GSM_SERVICE_BATCH_OPERATION_START);
            gsm_service_manager_add_batch (manager, batch);
            gsm_service_manager_queue_batch (manager);
          }
      }
      break;
    case 17:
      dump_services (manager, step / 2, "enable org.gnome.Keyring");  
      break;
#endif
    default:
      g_object_unref (manager);
      
      g_main_loop_quit (loop);
      break;
    }

  step++;

  return run_again;
}

int 
main (int argc, char *argv[])
{
  GMainLoop *loop;
  g_type_init ();
  loop = g_main_loop_new (NULL, FALSE);

  g_timeout_add_full (G_PRIORITY_DEFAULT_IDLE, 1000, test_timeout, loop,
                      (GDestroyNotify) g_main_loop_quit);

  g_main_loop_run (loop);
  
  return 0;
}
#endif
/* gsm-service-manager.h - Manage services
 *
 * Copyright (C) 2005 Matthias Clasen
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2, 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.  
 */
#ifndef GSM_SERVICE_MANAGER_H
#define GSM_SERVICE_MANAGER_H

#include <glib.h>
#include <glib-object.h>

G_BEGIN_DECLS

#define GSM_TYPE_SERVICE_MANAGER            (gsm_service_manager_get_type ())
#define GSM_SERVICE_MANAGER(obj)            (G_TYPE_CHECK_INSTANCE_CAST ((obj), GSM_TYPE_SERVICE_MANAGER, GsmServiceManager))
#define GSM_SERVICE_MANAGER_CLASS(klass)    (G_TYPE_CHECK_CLASS_CAST ((klass), GSM_TYPE_SERVICE_MANAGER, GsmServiceManagerClass))
#define GSM_IS_SERVICE_MANAGER(obj)         (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GSM_TYPE_SERVICE_MANAGER))
#define GSM_IS_SERVICE_MANAGER_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GSM_TYPE_SERVICE_MANAGER))
#define GSM_SERVICE_MANAGER_GET_CLASS(obj)  (G_TYPE_INSTANCE_GET_CLASS((obj), GSM_TYPE_SERVICE_MANAGER, GsmServiceManagerClass))
#define GSM_SERVICE_MANAGER_ERROR           (gsm_service_manager_error_quark ())


typedef struct _GsmServiceManager        GsmServiceManager;
typedef struct _GsmServiceManagerClass   GsmServiceManagerClass;
typedef struct _GsmServiceManagerPrivate GsmServiceManagerPrivate;
typedef enum   _GsmServiceManagerError   GsmServiceManagerError;

struct _GsmServiceManager {
  GObject parent;

  /*< private >*/

  GsmServiceManagerPrivate *priv;
};

struct _GsmServiceManagerClass {
  GObjectClass parent_class;

  /* Signals */

  /* Virtual functions */
  void (* startup_complete) (GsmServiceManager *manager);
};

enum _GsmServiceManagerError {
      GSM_SERVICE_MANAGER_ERROR_GENERIC = 0,
      GSM_SERVICE_MANAGER_SERVICE_NOT_FOUND
};

GType              gsm_service_manager_get_type         (void) G_GNUC_CONST;
GQuark             gsm_service_manager_error_quark      (void) G_GNUC_CONST;
GsmServiceManager *gsm_service_manager_new              (void);
gboolean           gsm_service_manager_connect_to_bus   (GsmServiceManager *manager);

void gsm_service_manager_disconnect_from_bus (GsmServiceManager *manager);

void               gsm_service_manager_start            (GsmServiceManager *manager);
void               gsm_service_manager_stop             (GsmServiceManager *manager);
G_END_DECLS

#endif  /* GSM_SERVICES_H */


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]