/* $Id: e2_hal.c 800 2008-02-09 22:21:01Z tpgww $

Copyright (C) 2008 tooar <tooar@gmx.net>

This file is part of emelFM2.
emelFM2 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 3, or (at your option)
any later version.

emelFM2 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 emelFM2; see the file GPL. If not, contact the Free Software
Foundation, 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/

/**
@file src/filesystem/e2_hal.c
@brief functions related to system hardware-abstraction-layer
*/

#include "emelfm2.h"

#ifdef E2_HAL

#include <string.h>
#include <dbus/dbus-glib-lowlevel.h>
#include <hal/libhal.h>
#include <hal/libhal-storage.h>

#define COMPUTER_UDI "/org/freedesktop/Hal/devices/computer"
#define COMPUTER_TYPE "system.formfactor"
#define VOLUME "volume"
#define IS_VOLUME "block.is_volume"
#define REMOVABLE "storage.removable"

static DBusConnection *connection = NULL;
static LibHalContext *ctx = NULL;
static GList *removable_devices = NULL; //list of HalStorageData's
typedef struct _HalStorageData
{
	gchar *udi;	//for checking which devices are removed
	gchar *device_path;
	gboolean ejectable;
} HalStorageData;
/*
//user data for HAL context
struct _HalContextData
{
} HalContextData;
*/
static void _e2_hal_add_device_cb (LibHalContext *ctx, const gchar *udi);
static void _e2_hal_remove_device_cb (LibHalContext *ctx, const gchar *udi);

/*
udi = '/org/freedesktop/Hal/devices/platform_floppy_0'
  info.bus = 'platform'  (string)
  info.parent = '/org/freedesktop/Hal/devices/computer'  (string)
  info.product = 'Platform Device (floppy.0)'  (string)
  info.subsystem = 'platform'  (string)
  info.udi = '/org/freedesktop/Hal/devices/platform_floppy_0'  (string)
  linux.hotplug_type = 2  (0x2)  (int)
  linux.subsystem = 'platform'  (string)
  linux.sysfs_path = '/sys/devices/platform/floppy.0'  (string)
  platform.id = 'floppy.0'  (string)

udi = '/org/freedesktop/Hal/devices/platform_floppy_0_storage'
  block.device = '/dev/fd0'  (string)
  block.is_volume = false  (bool)
  block.major = 2  (0x2)  (int)
  block.minor = 0  (0x0)  (int)
  block.storage_device = '/org/freedesktop/Hal/devices/platform_floppy_0_storage'  (string)
  info.capabilities = {'storage', 'block'} (string list)
  info.category = 'storage'  (string)
  info.interfaces = {'org.freedesktop.Hal.Device.Volume'} (string list)
  info.parent = '/org/freedesktop/Hal/devices/platform_floppy_0'  (string)
  info.product = 'PC Floppy Drive'  (string)
  info.udi = '/org/freedesktop/Hal/devices/platform_floppy_0_storage'  (string)
  info.vendor = ''  (string)
  linux.hotplug_type = 3  (0x3)  (int)
  linux.sysfs_path = '/sys/block/fd0'  (string)
  org.freedesktop.Hal.Device.Volume.method_argnames = {'mount_point fstype extra_options', 'extra_options', 'extra_options'} (string list)
  org.freedesktop.Hal.Device.Volume.method_execpaths = {'hal-storage-mount', 'hal-storage-unmount', 'hal-storage-eject'} (string list)
  org.freedesktop.Hal.Device.Volume.method_names = {'Mount', 'Unmount', 'Eject'} (string list)
  org.freedesktop.Hal.Device.Volume.method_signatures = {'ssas', 'as', 'as'} (string list)
  storage.automount_enabled_hint = true  (bool)
  storage.bus = 'platform'  (string)
  storage.drive_type = 'floppy'  (string)
  storage.hotpluggable = false  (bool)
  storage.media_check_enabled = false  (bool)
  storage.model = ''  (string)
  storage.no_partitions_hint = true  (bool)
  storage.originating_device = '/org/freedesktop/Hal/devices/platform_floppy_0'  (string)
  storage.physical_device = '/org/freedesktop/Hal/devices/platform_floppy_0'  (string)
  storage.removable = true  (bool)
  storage.removable.media_available = false  (bool)
  storage.requires_eject = false  (bool)
  storage.size = 0  (0x0)  (uint64)
  storage.vendor = 'PC Floppy Drive'  (string)
  volume.mount.valid_options = {'ro', 'sync', 'dirsync', 'noatime', 'nodiratime', 'noexec', 'quiet', 'remount', 'exec', 'utf8', 'shortname=', 'codepage=', 'iocharset=', 'umask=', 'uid='} (string list)
*/

/* *
@brief display hal/dbus error message

@param message specific message
@error dbus error data, or NULL

@return
*/
/*
static void _e2_hal_advise (const gchar *message, const DBusError *error)
{
	if (error != NULL && dbus_error_is_set (error))
	{
		gchar *msg = g_strdup_printf ("%s (%s)", message, error->message);
		e2_output_print_error (msg, TRUE);
	}
	else
		e2_output_print_error ((gchar *)message, FALSE);
}
*/
static gboolean _e2_hal_detect_current_removables (LibHalContext *ctx)
{
	gint i, count;
	gchar **devices;
	DBusError error;

	dbus_error_init (&error);
	devices = libhal_manager_find_device_string_match (ctx,
		"info.category", "storage", &count, &error);
	if (dbus_error_is_set (&error))
	{
//		_e2_hal_advise (_("HAL: cannot find any storage device"), &error);
		printd (WARN, "HAL: cannot find any storage device - %s", error.message);
		dbus_error_free (&error);
		return FALSE;
	}

	if (devices != NULL)
	{
		for (i = 0; i < count; i++)
		{
			if (devices[i] != NULL)
				_e2_hal_add_device_cb (ctx, devices[i]);
		}
		dbus_free_string_array (devices);
	}

	return TRUE;
}
/**
@brief callback for a device-add event
@param ctx the hal connection-context
@param udi string identifying the device added

@return
*/
static void _e2_hal_add_device_cb (LibHalContext *ctx, const gchar *udi)
{
	DBusError error;
	dbus_error_init (&error);
	if (libhal_device_property_exists (ctx, udi, "storage.removable", &error))
	{
		if (libhal_device_get_property_bool (ctx, udi, "storage.removable", &error))
		{
			HalStorageData *devicedata = ALLOCATE0 (HalStorageData);
			CHECKALLOCATEDWARN (devicedata, return;);

			devicedata->udi = g_strdup (udi);	//need a copy, original doesn't persist during removal
			devicedata->device_path = libhal_device_get_property_string (ctx, udi,
						"block.device", &error);
			printd (DEBUG, "HAL: storage %s is removable", devicedata->device_path);

			if (libhal_device_property_exists (ctx, udi, "storage.requires_eject", &error))
			{
				devicedata->ejectable = (gboolean) libhal_device_get_property_bool
					(ctx, udi, "storage.requires_eject", &error);
				if (devicedata->ejectable)
				{
					if (!libhal_device_add_property_watch (ctx, udi, &error))
					{
//						_e2_hal_advise (_("HAL: cannot set watch on device"), &error);
						printd (WARN, "HAL: cannot set watch on device - %s", error.message);
						dbus_error_free (&error);
					}
					else
					{
						printd (DEBUG, "HAL: Watch added to device udi %s", udi);
						printd (DEBUG, "storage will be ejected");
					}
				}
			}
			removable_devices = g_list_append (removable_devices, devicedata);
		}
		else
			dbus_error_free (&error);
	}
	else
		dbus_error_free (&error);
}
/**
@brief callback for a device-remove event
@param ctx the hal connection-context
@param udi string identifying the device removed

@return
*/
static void _e2_hal_remove_device_cb (LibHalContext *ctx, const gchar *udi)
{
	if (removable_devices != NULL)
	{
		GList *node;
		for (node = removable_devices; node != NULL; node = node->next)
		{
			HalStorageData *store;
			store = (HalStorageData *)node->data;
			if (strcmp (store->udi, udi) == 0)
			{
				if (store->ejectable)
				{
					DBusError error;
					dbus_error_init (&error);
					if (!libhal_device_remove_property_watch (ctx, udi, &error))
					{
//						_e2_hal_advise (_("HAL: cannot set watch on device"), &error);
						printd (WARN, "HAL: cannot set watch on device - %s", error.message);
						dbus_error_free (&error);
					}
					else
						printd (DEBUG, "HAL: Watch added to device udi %s", udi);
				}
				if (store->udi != NULL)
					g_free (store->udi);
				libhal_free_string (store->device_path);
				DEALLOCATE (HalStorageData, store);
				removable_devices = g_list_remove (removable_devices, store);
				break;
			}
		}
	}
}
/**
@brief callback for a property-change for a removable device that requires ejection
@param ctx the hal connection-context
@param udi string identifying the device whose property changed
@param key string identifying the property which changed
@param is_removed UNUSED TRUE when a property-change is a removal
@param is_added UNUSED TRUE when a property-change is an addition

@return
*/
static void _e2_hal_property_change_cb (LibHalContext *ctx, const gchar *udi,
	const gchar *key, dbus_bool_t is_removed, dbus_bool_t is_added)
{
	printd (DEBUG, "HAL: property changed for udi %s and key %s", udi, key);

	//interested in ejectable media property changes
	if (strcmp (key, "storage.removable.media_available") == 0)
	{
		DBusError error;

		dbus_error_init (&error);
		gboolean loaded = (gboolean) libhal_device_get_property_bool (ctx, udi,
				"storage.removable.media_available", &error);
		if (dbus_error_is_set (&error))
		{
//			_e2_hal_advise (_("HAL: cannot find media property"), &error);
			printd (WARN, "HAL: cannot find media property - %s", error.message);
			dbus_error_free (&error);
			return;
		}

		if (loaded)
		{
			printd (DEBUG, "HAL: removable media present now");
			//do stuff
			//mount if device mountpoint is not mounted (os should handle that)
		}
		else
		{
			printd (DEBUG, "HAL: removable media gone");
			//eject if not just done to trigger this callback
			//unmount if device mountpoint is mounted (os should handle that)
		}
	}
}
/**
@brief check whether native @a utfpath resides on a removable device
@param devpath /dev/<whatever>

@return TRUE if it's removable
*/
gboolean e2_hal_device_is_removable (const gchar *devpath)
{
	if (removable_devices != NULL)
	{
		GList *node;
		for (node = removable_devices; node != NULL; node = node->next)
		{
			if (strcmp (((HalStorageData *)node->data)->device_path, devpath) == 0)
				return TRUE;
		}
	}
	return FALSE;
}
/**
@brief check whether native @a utfpath resides on an ejectable device
@param devpath /dev/<whatever>

@return TRUE if it's ejectable
*/
gboolean e2_hal_device_is_ejectable (const gchar *devpath)
{
	if (removable_devices != NULL)
	{
		GList *node;
		for (node = removable_devices; node != NULL; node = node->next)
		{
			if (strcmp (((HalStorageData *)node->data)->device_path, devpath) == 0)
				return ((HalStorageData *)node->data)->ejectable;
		}
	}
	return FALSE;
}
/**
@brief initialise HAL connection

@return
*/
void e2_hal_init (void)
{
	DBusError error;

	ctx = libhal_ctx_new ();
	if (ctx == NULL)
	{
		printd (DEBUG, "Cannot create HAL context");
		return;
	}

	dbus_error_init (&error);
	printd (DEBUG, "connecting to DBUS");
	connection = dbus_bus_get (DBUS_BUS_SYSTEM, &error);
	if (connection == NULL)
	{
//		_e2_hal_advise (_("Cannot connect to system data bus"), &error);
		printd (WARN, "Cannot connect to system data bus - %s", error.message);
		goto cleanup;
	}

	libhal_ctx_set_dbus_connection (ctx, connection);

//	libhal_ctx_set_user_data (ctx, hal_user_data);
	libhal_ctx_set_device_added (ctx, _e2_hal_add_device_cb);
	libhal_ctx_set_device_removed (ctx, _e2_hal_remove_device_cb);
	libhal_ctx_set_device_property_modified (ctx, _e2_hal_property_change_cb);

	if (!libhal_ctx_init (ctx, &error))
	{
//		_e2_hal_advise (_("Cannot connect to system HAL"), &error);
		printd (WARN, "Cannot connect to system HAL - %s", error.message);
		goto cleanup;
	}

	if (!_e2_hal_detect_current_removables (ctx))
	{
//		_e2_hal_advise (_(""), NULL);
		printd (WARN, "Cannot determine mounted volumes at session start");
		goto cleanup;
	}

    if (!libhal_device_property_watch_all (ctx, &error))
	{
//		_e2_hal_advise (_("HAL: Cannot watch properties"), &error);
		printd (WARN, "HAL: Cannot watch properties - %s", error.message);
		goto cleanup;
	}
	dbus_connection_setup_with_g_main (connection, NULL);

	return;

cleanup:
	if (ctx != NULL)
	{
		libhal_ctx_free (ctx);
		ctx = NULL;
	}
	if (connection != NULL)
		dbus_connection_unref (connection);
	if (dbus_error_is_set (&error))
		dbus_error_free (&error);
}
/**
@brief end HAL connection and related cleanup

@return
*/
void e2_hal_disconnect (void)
{
	if (ctx != NULL)
	{
		DBusError error;

		dbus_error_init (&error);
		if (!libhal_ctx_shutdown (ctx, &error))
		{
			printd (WARN, "error disconnecting from HAL - %s", error.message);
			dbus_error_free (&error);
		}
		libhal_ctx_free (ctx);
	}
	if (connection != NULL)
		dbus_connection_unref (connection);

	if (removable_devices != NULL)
	{
		GList *node;
		HalStorageData *store;

		for (node = removable_devices; node != NULL; node = node->next)
		{
			store = (HalStorageData *)node->data;
			if (store->udi != NULL)
				g_free (store->udi);
			if (store->device_path != NULL)
				libhal_free_string (store->device_path );
			DEALLOCATE (HalStorageData, store);
		}
		g_list_free (removable_devices);
	}
}

#endif //def E2_HAL
