/* GIO - GLib Input, Output and Streaming Library
 * 
 * Copyright (C) 2006-2007 Red Hat, Inc.
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General
 * Public License along with this library; if not, write to the
 * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
 * Boston, MA 02110-1301, USA.
 *
 * Author: Alexander Larsson <alexl@redhat.com>
 */

#include <config.h>

#include <string.h>
#include <unistd.h>
#include <signal.h>

#include <glib.h>
#include <glib/gi18n.h>
#include <gio/gio.h>
#include "mount.h"
#include "gmountoperationdbus.h"
#include "gvfsdaemonprotocol.h"
#include <gvfsdbus.h>

typedef struct {
  char *display_name;
  char *stable_name;
  char *x_content_types;
  char *icon;
  char *prefered_filename_encoding;
  gboolean user_visible;
  char *default_location;
  char *fuse_mountpoint; /* Always set, even if fuse not available */
  
  /* Daemon object ref */
  char *dbus_id;
  char *object_path;
  guint name_watcher_id;

  /* Mount details */
  GMountSpec *mount_spec;
} VfsMount;

typedef struct  {
  char *type;
  char *exec;
  char *dbus_name;
  gboolean automount;
  char *scheme;
  char **scheme_aliases;
  int default_port;
  gboolean hostname_is_inet;
} VfsMountable; 

typedef void (*MountCallback) (VfsMountable *mountable,
			       GError *error,
			       gpointer user_data);

static GList *mountables = NULL;
static GList *mounts = NULL;

static gboolean fuse_available;

static GVfsDBusMountTracker *mount_tracker = NULL;


static void lookup_mount (GVfsDBusMountTracker *object,
                          GDBusMethodInvocation *invocation,
                          GMountSpec *spec,
                          gboolean do_automount);

static VfsMount *
find_vfs_mount (const char *dbus_id,
		const char *obj_path)
{
  GList *l;
  for (l = mounts; l != NULL; l = l->next)
    {
      VfsMount *mount = l->data;

      if (strcmp (mount->dbus_id, dbus_id) == 0 &&
	  strcmp (mount->object_path, obj_path) == 0)
	return mount;
    }
  
  return NULL;
}

static VfsMount *
find_vfs_mount_by_fuse_path (const char *fuse_path)
{
  GList *l;

  if (!fuse_available)
    return NULL;
  
  for (l = mounts; l != NULL; l = l->next)
    {
      VfsMount *mount = l->data;

      if (mount->fuse_mountpoint != NULL &&
	  g_str_has_prefix (fuse_path, mount->fuse_mountpoint))
	{
	  int len = strlen (mount->fuse_mountpoint);
	  if (fuse_path[len] == 0 ||
	      fuse_path[len] == '/')
	    return mount;
	}
    }
  
  return NULL;
}

static VfsMount *
match_vfs_mount (GMountSpec *match)
{
  GList *l;
  for (l = mounts; l != NULL; l = l->next)
    {
      VfsMount *mount = l->data;

      if (g_mount_spec_match (mount->mount_spec, match))
	return mount;
    }
  
  return NULL;
}

static VfsMountable *
find_mountable (const char *type)
{
  GList *l;

  for (l = mountables; l != NULL; l = l->next)
    {
      VfsMountable *mountable = l->data;

      if (strcmp (mountable->type, type) == 0)
	return mountable;
    }
  
  return NULL;
}

static VfsMountable *
lookup_mountable (GMountSpec *spec)
{
  const char *type;
  
  type = g_mount_spec_get_type (spec);
  if (type == NULL)
    return NULL;

  return find_mountable (type);
}

static void
vfs_mountable_free (VfsMountable *mountable)
{
  g_free (mountable->type);
  g_free (mountable->exec);
  g_free (mountable->dbus_name);
  g_free (mountable->scheme);
  g_strfreev (mountable->scheme_aliases);
  g_free (mountable);
}

static void
vfs_mount_free (VfsMount *mount)
{
  if (mount->name_watcher_id != 0)
    g_bus_unwatch_name (mount->name_watcher_id);

  g_free (mount->display_name);
  g_free (mount->stable_name);
  g_free (mount->x_content_types);
  g_free (mount->icon);
  g_free (mount->fuse_mountpoint);
  g_free (mount->prefered_filename_encoding);
  g_free (mount->default_location);
  g_free (mount->dbus_id);
  g_free (mount->object_path);
  g_mount_spec_unref (mount->mount_spec);

  g_free (mount);
}


/* Keep in sync with dbus-interfaces.xml */
#define VFS_MOUNT_ARRAY_DBUS_STRUCT_TYPE "a(sosssssbay(aya{sv})ay)"
#define VFS_MOUNTABLE_ARRAY_DBUS_STRUCT_TYPE "a(ssasib)"

static GVariant *
vfs_mount_to_dbus (VfsMount *mount)
{
  return g_variant_new ("(sosssssb^ay@(aya{sv})^ay)",
                        mount->dbus_id,
                        mount->object_path,
                        mount->display_name,
                        mount->stable_name,
                        mount->x_content_types,
                        mount->icon,
                        mount->prefered_filename_encoding,
                        mount->user_visible,
                        (fuse_available && mount->fuse_mountpoint) ? mount->fuse_mountpoint : "",
                        g_mount_spec_to_dbus (mount->mount_spec),
                        mount->default_location ? mount->default_location : "");
}

static GVariant *
vfs_mountable_to_dbus (VfsMountable *mountable)
{
  char *empty[] = {NULL};
  
  return g_variant_new ("(ss^asib)",
                        mountable->type,
                        mountable->scheme ? mountable->scheme : "",
                        mountable->scheme_aliases ? mountable->scheme_aliases : empty,
                        mountable->default_port,
                        mountable->hostname_is_inet);
}


/************************************************************************
 * Support for mounting a VfsMountable                                  *
 ************************************************************************/


typedef struct {
  VfsMountable *mountable;
  gboolean automount;
  GMountSource *source;
  GMountSpec *mount_spec;
  MountCallback callback;
  gpointer user_data;
  char *obj_path;
  gboolean spawned;
  GVfsDBusSpawner *spawner;
} MountData;

static void spawn_mount (MountData *data);

static void
mount_data_free (MountData *data)
{
  g_object_unref (data->source);
  g_mount_spec_unref (data->mount_spec);
  g_free (data->obj_path);
  g_clear_object (&data->spawner);

  g_free (data);
}

static void
mount_finish (MountData *data, GError *error)
{
  data->callback (data->mountable, error, data->user_data);
  mount_data_free (data);
}

static void
dbus_mount_reply (GVfsDBusMountable *proxy,
                  GAsyncResult  *res,
                  gpointer user_data)
{
  GError *error = NULL;
  MountData *data = user_data;

  if (!gvfs_dbus_mountable_call_mount_finish (proxy,
                                              res,
                                              &error))
    {
      if ((g_error_matches (error, G_DBUS_ERROR, G_DBUS_ERROR_NAME_HAS_NO_OWNER) ||
           g_error_matches (error, G_DBUS_ERROR, G_DBUS_ERROR_SERVICE_UNKNOWN)) &&
           !data->spawned)
        spawn_mount (data);
      else
        {
          g_warning ("dbus_mount_reply: Error from org.gtk.vfs.Mountable.mount(): %s", error->message);
          mount_finish (data, error);
          g_error_free (error);
        }
    }
  else
    {
      mount_finish (data, NULL);
    }
}

static void
mountable_mount_proxy_cb (GObject *source_object,
                          GAsyncResult *res,
                          gpointer user_data)
{
  GVfsDBusMountable *proxy;
  GError *error = NULL;
  MountData *data = user_data;
  
  proxy = gvfs_dbus_mountable_proxy_new_for_bus_finish (res, &error);

  if (proxy == NULL)
    {
      g_printerr ("mountable_mount_proxy_cb: Error creating proxy: %s (%s, %d)\n",
                  error->message, g_quark_to_string (error->domain), error->code);
      mount_finish (data, error);
      g_error_free (error);
      return;
    }

  g_dbus_proxy_set_default_timeout (G_DBUS_PROXY (proxy), 
                                    G_VFS_DBUS_MOUNT_TIMEOUT_MSECS);
  
  gvfs_dbus_mountable_call_mount (proxy,
                                  g_mount_spec_to_dbus (data->mount_spec),
                                  data->automount,
                                  g_mount_source_to_dbus (data->source),
                                  NULL,
                                  (GAsyncReadyCallback) dbus_mount_reply, data);

  g_object_unref (proxy);
}

static void
mountable_mount_with_name (MountData *data,
			   const char *dbus_name)
{
  gvfs_dbus_mountable_proxy_new_for_bus (G_BUS_TYPE_SESSION,
                                         G_DBUS_PROXY_FLAGS_DO_NOT_CONNECT_SIGNALS | G_DBUS_PROXY_FLAGS_DO_NOT_LOAD_PROPERTIES,
                                         dbus_name,
                                         G_VFS_DBUS_MOUNTABLE_PATH,
                                         NULL,
                                         mountable_mount_proxy_cb,
                                         data);
}

static gboolean
spawn_mount_handle_spawned (GVfsDBusSpawner *object,
                            GDBusMethodInvocation *invocation,
                            gboolean arg_succeeded,
                            const gchar *arg_error_message,
                            gpointer user_data)
{
  MountData *data = user_data;
  GError *error;

  g_dbus_interface_skeleton_unexport (G_DBUS_INTERFACE_SKELETON (data->spawner));
  
  if (!arg_succeeded)
    {
      g_set_error_literal (&error, G_IO_ERROR, G_IO_ERROR_FAILED, arg_error_message);
      mount_finish (data, error);
      g_error_free (error);
    }
  else
    {
      mountable_mount_with_name (data, g_dbus_method_invocation_get_sender (invocation));
    }
  
  gvfs_dbus_spawner_complete_spawned (object, invocation);
  
  return TRUE;
}

static void
spawn_mount (MountData *data)
{
  char *exec;
  GError *error;
  GDBusConnection *connection;
  static int mount_id = 0;

  data->spawned = TRUE;
  
  error = NULL;
  if (data->mountable->exec == NULL)
    {
      g_set_error_literal (&error, G_IO_ERROR, G_IO_ERROR_FAILED,
			   "No exec key defined for mountpoint");
      mount_finish (data, error);
      g_error_free (error);
    }
  else
    {
      data->obj_path = g_strdup_printf ("/org/gtk/gvfs/exec_spaw/%d", mount_id++);

      connection = g_bus_get_sync (G_BUS_TYPE_SESSION, NULL, &error);
      if (! connection)
        {
          mount_finish (data, error);
          g_error_free (error);
          return;
        }
      
      data->spawner = gvfs_dbus_spawner_skeleton_new ();
      g_signal_connect (data->spawner, "handle-spawned", G_CALLBACK (spawn_mount_handle_spawned), data);
      
      if (!g_dbus_interface_skeleton_export (G_DBUS_INTERFACE_SKELETON (data->spawner),
                                             connection,
                                             data->obj_path, 
                                             &error))
        {
          mount_finish (data, error);
          g_error_free (error);
          g_object_unref (connection);
          return;
        }

      exec = g_strconcat (data->mountable->exec, " --spawner ", g_dbus_connection_get_unique_name (connection), " ", data->obj_path, NULL);
      if (!g_spawn_command_line_async (exec, &error))
	{
          g_dbus_interface_skeleton_unexport (G_DBUS_INTERFACE_SKELETON (data->spawner));
	  mount_finish (data, error);
	  g_error_free (error);
	}
      
      /* TODO: Add a timeout here to detect spawned app crashing */
      
      g_object_unref (connection);
      g_free (exec);
    }
}

static void
mountable_mount (VfsMountable *mountable,
		 GMountSpec *mount_spec,
		 GMountSource *source,
		 gboolean automount,
		 MountCallback callback,
		 gpointer user_data)
{
  MountData *data;

  data = g_new0 (MountData, 1);
  data->automount = automount;
  data->mountable = mountable;
  data->source = g_object_ref (source);
  data->mount_spec = g_mount_spec_ref (mount_spec);
  data->callback = callback;
  data->user_data = user_data;

  if (mountable->dbus_name == NULL)
    spawn_mount (data);
  else
    mountable_mount_with_name (data, mountable->dbus_name);
}

static void
read_mountable_config (void)
{
  GDir *dir;
  char *mount_dir, *path;
  const char *filename;
  GKeyFile *keyfile;
  char **types;
  VfsMountable *mountable;
  int i;
  
  mount_dir = MOUNTABLE_DIR;
  dir = g_dir_open (mount_dir, 0, NULL);

  if (dir)
    {
      while ((filename = g_dir_read_name (dir)) != NULL)
	{
	  path = g_build_filename (mount_dir, filename, NULL);
	  
	  keyfile = g_key_file_new ();
	  if (g_key_file_load_from_file (keyfile, path, G_KEY_FILE_NONE, NULL))
	    {
	      types = g_key_file_get_string_list (keyfile, "Mount", "Type", NULL, NULL);
	      if (types != NULL)
		{
		  for (i = 0; types[i] != NULL; i++)
		    {
		      if (*types[i] != 0)
			{
			  mountable = g_new0 (VfsMountable, 1);
			  mountable->type = g_strdup (types[i]);
			  mountable->exec = g_key_file_get_string (keyfile, "Mount", "Exec", NULL);
			  mountable->dbus_name = g_key_file_get_string (keyfile, "Mount", "DBusName", NULL);
			  mountable->automount = g_key_file_get_boolean (keyfile, "Mount", "AutoMount", NULL);
			  mountable->scheme = g_key_file_get_string (keyfile, "Mount", "Scheme", NULL);
			  mountable->scheme_aliases =
			    g_key_file_get_string_list (keyfile, "Mount", "SchemeAliases", NULL, NULL);
			  mountable->default_port = g_key_file_get_integer (keyfile, "Mount", "DefaultPort", NULL);
			  mountable->hostname_is_inet = g_key_file_get_boolean (keyfile, "Mount", "HostnameIsInetAddress", NULL);

			  if (mountable->scheme == NULL)
			    mountable->scheme = g_strdup (mountable->type);
			  
			  mountables = g_list_prepend (mountables, mountable);
			}
		    }
		  g_strfreev (types);
		}
	    }
	  g_key_file_free (keyfile);
	  g_free (path);
	}
      g_dir_close (dir);
    }
}

static void
re_read_mountable_config (void)
{
  g_list_foreach (mountables, (GFunc)vfs_mountable_free, NULL);
  g_list_free (mountables);
  mountables = NULL;

  read_mountable_config ();
}

/************************************************************************
 * Support for keeping track of active mounts                           *
 ************************************************************************/

static void
signal_mounted_unmounted (VfsMount *mount,
			  gboolean mounted)
{
  if (mounted)
    gvfs_dbus_mount_tracker_emit_mounted (mount_tracker, vfs_mount_to_dbus (mount));
  else
    gvfs_dbus_mount_tracker_emit_unmounted (mount_tracker, vfs_mount_to_dbus (mount));
}

static void
dbus_client_disconnected (const char *dbus_id)
{
  GList *l, *next;

  next = NULL;
  for (l = mounts; l != NULL; l = next)
    {
      VfsMount *mount = l->data;
      next = l->next;

      if (strcmp (mount->dbus_id, dbus_id) == 0)
	{
	  signal_mounted_unmounted (mount, FALSE);
	  
	  vfs_mount_free (mount);
	  mounts = g_list_delete_link (mounts, l);
	}
    }
}

static void
name_vanished_cb (GDBusConnection *connection,
                  const gchar *name,
                  gpointer user_data)
{
  dbus_client_disconnected (name);
}

static gboolean
handle_register_mount (GVfsDBusMountTracker *object,
                       GDBusMethodInvocation *invocation,
                       const gchar *arg_obj_path,
                       const gchar *arg_display_name,
                       const gchar *arg_stable_name,
                       const gchar *arg_x_content_types,
                       const gchar *arg_icon,
                       const gchar *arg_prefered_filename_encoding,
                       gboolean arg_user_visible,
                       GVariant *arg_mount_spec,
                       const gchar *arg_default_location,
                       gpointer user_data)
{
  VfsMount *mount;
  const char *id;
  GMountSpec *mount_spec;

  id = g_dbus_method_invocation_get_sender (invocation);

  if (find_vfs_mount (id, arg_obj_path) != NULL) {
    g_dbus_method_invocation_return_error_literal (invocation,
                                                   G_IO_ERROR,
                                                   G_IO_ERROR_ALREADY_MOUNTED,
                                                   "Mountpoint Already registered");
  }
  else if ((mount_spec = g_mount_spec_from_dbus (arg_mount_spec)) == NULL) {
    g_dbus_method_invocation_return_error_literal (invocation, 
                                                   G_IO_ERROR,
                                                   G_IO_ERROR_INVALID_ARGUMENT,
                                                   "Error in mount spec");
  }
  else if (match_vfs_mount (mount_spec) != NULL) {
    g_dbus_method_invocation_return_error_literal (invocation,
                                                   G_IO_ERROR,
                                                   G_IO_ERROR_ALREADY_MOUNTED,
                                                   "Mountpoint Already registered");
  }
  else
    {
      mount = g_new0 (VfsMount, 1);
      mount->display_name = g_strdup (arg_display_name);
      mount->stable_name = g_strdup (arg_stable_name);
      mount->x_content_types = g_strdup (arg_x_content_types);
      mount->icon = g_strdup (arg_icon);
      mount->prefered_filename_encoding = g_strdup (arg_prefered_filename_encoding);
      mount->user_visible = arg_user_visible;
      mount->dbus_id = g_strdup (id);
      mount->object_path = g_strdup (arg_obj_path);
      mount->mount_spec = mount_spec;

      if (arg_default_location)  
        mount->default_location = g_strdup (arg_default_location);
      else
        mount->default_location = g_strdup ("");

      if (arg_user_visible)
        {
          char *fs_name;
          
          /* Keep in sync with fuse daemon */
          fs_name = g_uri_escape_string (mount->stable_name, "+@#$., ", TRUE);
          
          /* Use the old .gvfs location as fallback, not .cache/gvfs */
          if (g_get_user_runtime_dir() == g_get_user_cache_dir ())
            mount->fuse_mountpoint = g_build_filename (g_get_home_dir(), ".gvfs", fs_name, NULL);
          else
            mount->fuse_mountpoint = g_build_filename (g_get_user_runtime_dir(), "gvfs", fs_name, NULL);
        }
      
      mounts = g_list_prepend (mounts, mount);

      /* watch the mount for being disconnected */
      mount->name_watcher_id = g_bus_watch_name (G_BUS_TYPE_SESSION,
                                                 id,
                                                 G_BUS_NAME_WATCHER_FLAGS_NONE,
                                                 NULL,
                                                 name_vanished_cb,
                                                 NULL,
                                                 NULL); 

      signal_mounted_unmounted (mount, TRUE);

      gvfs_dbus_mount_tracker_complete_register_mount (object, invocation);
    }
  
  return TRUE;
}

typedef struct {
  GVfsDBusMountTracker *object;
  GDBusMethodInvocation *invocation;
  GMountSpec *spec;
} AutoMountData;

static void
automount_done (VfsMountable *mountable,
		GError *error,
		gpointer _data)
{
  AutoMountData *data = _data;
  
  if (error)
    g_dbus_method_invocation_return_error (data->invocation,
                                           G_IO_ERROR, G_IO_ERROR_NOT_MOUNTED,
                                           _("Automount failed: %s"), error->message);
  else
    lookup_mount (data->object,
                  data->invocation,
                  data->spec,
                  FALSE);

  g_object_unref (data->object);
  g_object_unref (data->invocation);
  g_mount_spec_unref (data->spec);
  g_free (data);
}

static void
maybe_automount (GMountSpec *spec,
                 GVfsDBusMountTracker *object,
                 GDBusMethodInvocation *invocation,
                 gboolean do_automount)
{
  VfsMountable *mountable;

  mountable = lookup_mountable (spec);

  if (mountable != NULL && do_automount && mountable->automount)
    {
      AutoMountData *data;
      GMountSource *mount_source;

      g_debug ("automounting...\n");

      mount_source = g_mount_source_new_dummy ();

      data = g_new0 (AutoMountData, 1);
      data->object = g_object_ref (object);
      data->invocation = g_object_ref (invocation);
      data->spec = g_mount_spec_ref (spec);
      
      mountable_mount (mountable, spec, mount_source, TRUE, automount_done, data);
      g_object_unref (mount_source);
    }
  else if (mountable != NULL)
    g_dbus_method_invocation_return_error_literal (invocation,
                                                   G_IO_ERROR,
                                                   G_IO_ERROR_NOT_MOUNTED,
                                                   _("The specified location is not mounted"));
  else
    g_dbus_method_invocation_return_error_literal (invocation,
                                                   G_IO_ERROR,
                                                   G_IO_ERROR_NOT_SUPPORTED,
                                                   _("The specified location is not supported"));
}

static void
lookup_mount (GVfsDBusMountTracker *object,
              GDBusMethodInvocation *invocation,
              GMountSpec *spec,
              gboolean do_automount)
{
  VfsMount *mount;

  mount = match_vfs_mount (spec);
  if (mount == NULL)
    maybe_automount (spec, object, invocation, do_automount);
  else
    gvfs_dbus_mount_tracker_complete_lookup_mount (object, invocation,
                                                   vfs_mount_to_dbus (mount)); 
}

static gboolean 
handle_lookup_mount (GVfsDBusMountTracker *object,
                     GDBusMethodInvocation *invocation,
                     GVariant *arg_mount_spec,
                     gpointer user_data)
{
  GMountSpec *spec;

  spec = g_mount_spec_from_dbus (arg_mount_spec);

  if (spec != NULL)
    {
      lookup_mount (object, invocation, spec, TRUE);
      g_mount_spec_unref (spec);
    }
  else
    g_dbus_method_invocation_return_error_literal (invocation,
                                                   G_IO_ERROR,
                                                   G_IO_ERROR_INVALID_ARGUMENT,
                                                   "Invalid arguments");
  return TRUE;
}

static gboolean
handle_lookup_mount_by_fuse_path (GVfsDBusMountTracker *object,
                                  GDBusMethodInvocation *invocation,
                                  const gchar *arg_fuse_path,
                                  gpointer user_data)
{
  VfsMount *mount;

  mount = find_vfs_mount_by_fuse_path (arg_fuse_path);

  if (mount == NULL)
    g_dbus_method_invocation_return_error_literal (invocation,
                                                   G_IO_ERROR,
                                                   G_IO_ERROR_NOT_MOUNTED,
                                                   _("The specified location is not mounted"));
  else
    gvfs_dbus_mount_tracker_complete_lookup_mount_by_fuse_path (object,
                                                                invocation,
                                                                vfs_mount_to_dbus (mount));
  
  return TRUE;
}

static gboolean
handle_list_mounts (GVfsDBusMountTracker *object,
                    GDBusMethodInvocation *invocation,
                    gpointer user_data)
{
  GList *l;
  GVariantBuilder mounts_array;

  g_variant_builder_init (&mounts_array, G_VARIANT_TYPE (VFS_MOUNT_ARRAY_DBUS_STRUCT_TYPE));
  for (l = mounts; l != NULL; l = l->next)
    g_variant_builder_add_value (&mounts_array, vfs_mount_to_dbus (l->data));
  
  gvfs_dbus_mount_tracker_complete_list_mounts (object, invocation,
                                                g_variant_builder_end (&mounts_array));
  
  return TRUE;
}

static void
mount_location_done  (VfsMountable *mountable,
		      GError *error,
		      gpointer user_data)
{
  GDBusMethodInvocation *invocation = G_DBUS_METHOD_INVOCATION (user_data);
  
  if (error)
    g_dbus_method_invocation_return_gerror (invocation, error);
  else
    /* alas gvfs_dbus_mount_tracker_complete_mount_location() */
    g_dbus_method_invocation_return_value (invocation, g_variant_new ("()"));

  g_object_unref (invocation);
}

static gboolean 
handle_mount_location (GVfsDBusMountTracker *object,
                       GDBusMethodInvocation *invocation,
                       GVariant *arg_mount_spec,
                       GVariant *arg_mount_source,
                       gpointer user_data)
{
  GMountSpec *spec;
  VfsMountable *mountable;

  spec = g_mount_spec_from_dbus (arg_mount_spec);
  if (spec == NULL)
    g_dbus_method_invocation_return_error_literal (invocation,
                                                   G_IO_ERROR,
                                                   G_IO_ERROR_INVALID_ARGUMENT,
                                                   "Invalid arguments");
  else
    {
      VfsMount *mount;
      mount = match_vfs_mount (spec);
      
      if (mount != NULL)
        g_dbus_method_invocation_return_error_literal (invocation,
                                                       G_IO_ERROR,
                                                       G_IO_ERROR_ALREADY_MOUNTED,
                                                       _("Location is already mounted"));
      else
        {
          mountable = lookup_mountable (spec);
          
          if (mountable == NULL)
            g_dbus_method_invocation_return_error_literal (invocation,
                                                           G_IO_ERROR,
                                                           G_IO_ERROR_NOT_MOUNTED,
                                                           _("Location is not mountable"));
          else
            {
              GMountSource *source;

              source = g_mount_source_from_dbus (arg_mount_source);
              mountable_mount (mountable,
                               spec,
                               source,
                               FALSE,
                               mount_location_done, g_object_ref (invocation));
              g_object_unref (source);
            }
        }
    }
  
  if (spec)
    g_mount_spec_unref (spec);

  return TRUE;
}

static gboolean
handle_list_mount_types (GVfsDBusMountTracker *object,
                         GDBusMethodInvocation *invocation,
                         gpointer user_data)
{
  VfsMountable *mountable;
  GPtrArray *types;
  GList *l;

  types = g_ptr_array_new ();
  for (l = mountables; l != NULL; l = l->next)
    {
      mountable = l->data;
      g_ptr_array_add (types, (gpointer) mountable->type);
    }
  g_ptr_array_add (types, NULL);

  gvfs_dbus_mount_tracker_complete_list_mount_types (object, invocation,
                                                     (const gchar *const *) types->pdata);

  g_ptr_array_free (types, TRUE);
  return TRUE;
}

static gboolean
handle_list_mountable_info (GVfsDBusMountTracker *object,
                            GDBusMethodInvocation *invocation,
                            gpointer user_data)
{
  GList *l;
  GVariantBuilder mountables_array;
  
  g_variant_builder_init (&mountables_array, G_VARIANT_TYPE (VFS_MOUNTABLE_ARRAY_DBUS_STRUCT_TYPE));
  for (l = mountables; l != NULL; l = l->next) {
    g_variant_builder_add_value (&mountables_array, vfs_mountable_to_dbus (l->data));
  }
  
  gvfs_dbus_mount_tracker_complete_list_mountable_info (object, invocation,
                                                        g_variant_builder_end (&mountables_array));
  
  return TRUE;
}

static gboolean 
handle_register_fuse (GVfsDBusMountTracker *object,
                      GDBusMethodInvocation *invocation,
                      gpointer user_data)
{
  fuse_available = TRUE;
  gvfs_dbus_mount_tracker_complete_register_fuse (object, invocation);
  
  return TRUE;
}


static int reload_pipes[2];

static void
sigusr1_handler (int sig)
{
  while (write (reload_pipes[1], "a", 1) != 1)
    ;
}

static gboolean
reload_pipes_cb (GIOChannel *io,
		 GIOCondition condition,
		 gpointer data)
{
  char a;
  
  while (read (reload_pipes[0], &a, 1) != 1)
    ;

  re_read_mountable_config ();
  
  return TRUE;
}

gboolean
mount_init (void)
{
  GDBusConnection *conn;
  struct sigaction sa;
  GIOChannel *io;
  GError *error;
  gboolean res;
  
  res = TRUE;

  read_mountable_config ();

  if (pipe (reload_pipes) != -1)
    {
      io = g_io_channel_unix_new (reload_pipes[0]);
      g_io_add_watch (io, G_IO_IN, reload_pipes_cb, NULL);
      
      sa.sa_handler = sigusr1_handler;
      sigemptyset (&sa.sa_mask);
      sa.sa_flags = 0;
      sigaction (SIGUSR1, &sa, NULL);
    }
  
  
  error = NULL;
  conn = g_bus_get_sync (G_BUS_TYPE_SESSION, NULL, &error);
  if (! conn)
    {
      g_warning ("Error connecting to session bus: %s (%s, %d)\n",
                  error->message, g_quark_to_string (error->domain), error->code);
      g_error_free (error);
      return FALSE;
    }
  
  mount_tracker = gvfs_dbus_mount_tracker_skeleton_new ();

  g_signal_connect (mount_tracker, "handle-register-fuse", G_CALLBACK (handle_register_fuse), NULL);
  g_signal_connect (mount_tracker, "handle-register-mount", G_CALLBACK (handle_register_mount), NULL);
  g_signal_connect (mount_tracker, "handle-mount-location", G_CALLBACK (handle_mount_location), NULL);
  g_signal_connect (mount_tracker, "handle-lookup-mount", G_CALLBACK (handle_lookup_mount), NULL);
  g_signal_connect (mount_tracker, "handle-lookup-mount-by-fuse-path", G_CALLBACK (handle_lookup_mount_by_fuse_path), NULL);
  g_signal_connect (mount_tracker, "handle-list-mounts", G_CALLBACK (handle_list_mounts), NULL);
  g_signal_connect (mount_tracker, "handle-list-mountable-info", G_CALLBACK (handle_list_mountable_info), NULL);
  g_signal_connect (mount_tracker, "handle-list-mount-types", G_CALLBACK (handle_list_mount_types), NULL);
  /* FIXME: handle unregisterMount() */
  
  error = NULL;
  if (!g_dbus_interface_skeleton_export (G_DBUS_INTERFACE_SKELETON (mount_tracker), conn,
                                         G_VFS_DBUS_MOUNTTRACKER_PATH, &error))
    {
      g_warning ("Error exporting mount tracker: %s (%s, %d)\n",
                  error->message, g_quark_to_string (error->domain), error->code);
      g_error_free (error);
      g_object_unref (mount_tracker);
      mount_tracker = NULL;
      res = FALSE;
    }
  g_object_unref (conn);
  
  return res;
}

void
mount_finalize (void)
{
  if (mount_tracker != NULL)
    {
      g_dbus_interface_skeleton_unexport (G_DBUS_INTERFACE_SKELETON (mount_tracker));
      g_object_unref (mount_tracker);
    }
}
