/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */
/* gnome-vfs-volume-monitor-daemon.c - daemon implementation of volume handling

   Copyright (C) 2003 Red Hat, Inc

   The Gnome Library is free software; you can redistribute it and/or
   modify it under the terms of the GNU Library General Public License as
   published by the Free Software Foundation; either version 2 of the
   License, or (at your option) any later version.

   The Gnome 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
   Library General Public License for more details.

   You should have received a copy of the GNU Library General Public
   License along with the Gnome Library; see the file COPYING.LIB.  If not,
   write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
   Boston, MA 02111-1307, USA.

   Author: Alexander Larsson <alexl@redhat.com>
*/

#include <config.h>

#include <string.h>
#include <stdlib.h>
#include <libgnomevfs/gnome-vfs-utils.h>
#include <glib/gi18n-lib.h>

#include "gnome-vfs-volume-monitor-daemon.h"
#include "gnome-vfs-volume-monitor-private.h"
#include "gnome-vfs-unix-mounts.h"
#include "gnome-vfs-cdrom.h"
#include "gnome-vfs-filesystem-type.h"

#ifdef USE_HAL
#include "gnome-vfs-hal-mounts.h"
#endif /* USE_HAL */

static void gnome_vfs_volume_monitor_daemon_class_init (GnomeVFSVolumeMonitorDaemonClass *klass);
static void gnome_vfs_volume_monitor_daemon_init       (GnomeVFSVolumeMonitorDaemon      *volume_monitor_daemon);
static void gnome_vfs_volume_monitor_daemon_finalize   (GObject                          *object);

static void update_fstab_drives (GnomeVFSVolumeMonitorDaemon *volume_monitor_daemon);
static void update_mtab_volumes (GnomeVFSVolumeMonitorDaemon *volume_monitor_daemon);

static void update_connected_servers (GnomeVFSVolumeMonitorDaemon *volume_monitor_daemon);

typedef struct {
	char *id;
	char *uri;
	char *display_name;
	char *display_name_key;
	char *icon;
} GnomeVFSConnectedServer;

static GnomeVFSVolumeMonitorClass *parent_class = NULL;

/* In the event we can't connect to the HAL daemon this boolean is TRUE
 * and we fall back to the usual fstab/mtab monitoring. 
 *
 * This is also useful for maintaing the non-HAL code on a system with
 * HAL installed.
 */
#ifdef USE_HAL
static gboolean dont_use_hald = TRUE;
#endif /* USE_HAL */

/* Locking strategy:
 *
 * Its important that the public API is threadsafe, since it will be used
 * to implement gnome-vfs backends (which are threaded).
 * This is handled in various ways:
 * a) volumes/drives are mostly read-only, and for the few items that
 *    can change (refcount, drive/volume pointer) we lock on getters
 *    and setters
 * b) Changes to the volume monitor itself is only done by the main thread.
 *    This means we don't have to protect main thread reads of the state, since
 *    the data won't change unpredictably.
 * c) All writes to the volume manager thread are protected by the volume monitor
 *    lock, and all public API that reads this state also uses this lock.
 *
 */

GType
gnome_vfs_volume_monitor_daemon_get_type (void)
{
	static GType volume_monitor_daemon_type = 0;

	if (!volume_monitor_daemon_type) {
		const GTypeInfo volume_monitor_daemon_info = {
			sizeof (GnomeVFSVolumeMonitorDaemonClass),
			NULL,		/* base_init */
			NULL,		/* base_finalize */
			(GClassInitFunc) gnome_vfs_volume_monitor_daemon_class_init,
			NULL,		/* class_finalize */
			NULL,		/* class_data */
			sizeof (GnomeVFSVolumeMonitorDaemon),
			0,              /* n_preallocs */
			(GInstanceInitFunc) gnome_vfs_volume_monitor_daemon_init
		};
		
		volume_monitor_daemon_type =
			g_type_register_static (GNOME_VFS_TYPE_VOLUME_MONITOR, "GnomeVFSVolumeMonitorDaemon",
						&volume_monitor_daemon_info, 0);
	}
	
	return volume_monitor_daemon_type;
}

static void
gnome_vfs_volume_monitor_daemon_class_init (GnomeVFSVolumeMonitorDaemonClass *class)
{
	GObjectClass *o_class;
	
	parent_class = g_type_class_peek_parent (class);
	
	o_class = (GObjectClass *) class;
	
	/* GObject signals */
	o_class->finalize = gnome_vfs_volume_monitor_daemon_finalize;
}

static void
fstab_changed (gpointer data)
{
	GnomeVFSVolumeMonitorDaemon *volume_monitor;

	volume_monitor = data;
	update_fstab_drives (volume_monitor);
}

static void
mtab_changed (gpointer data)
{
	GnomeVFSVolumeMonitorDaemon *volume_monitor;

	volume_monitor = data;
	update_mtab_volumes (volume_monitor);
}

static void
connected_servers_changed (GConfClient* client,
			   guint cnxn_id,
			   GConfEntry *entry,
			   gpointer data)
{
	GnomeVFSVolumeMonitorDaemon *volume_monitor;

	volume_monitor = data;
	update_connected_servers (volume_monitor);
}


static void
gnome_vfs_volume_monitor_daemon_init (GnomeVFSVolumeMonitorDaemon *volume_monitor_daemon)
{
	volume_monitor_daemon->gconf_client = gconf_client_get_default ();

#ifdef USE_HAL
	if (_gnome_vfs_hal_mounts_init (volume_monitor_daemon)) {
		/* It worked, do use HAL */
		dont_use_hald = FALSE;
	} else {
		/* Couldn't connect to HAL daemon, don't use HAL */
		dont_use_hald = TRUE;
	}
#endif /* USE_HAL */

	_gnome_vfs_monitor_unix_mounts (fstab_changed,
					volume_monitor_daemon,
					mtab_changed,
					volume_monitor_daemon);

	gconf_client_add_dir (volume_monitor_daemon->gconf_client,
			      CONNECTED_SERVERS_DIR,
			      GCONF_CLIENT_PRELOAD_RECURSIVE,
			      NULL);

	volume_monitor_daemon->connected_id =
		gconf_client_notify_add (volume_monitor_daemon->gconf_client,
					 CONNECTED_SERVERS_DIR,
					 connected_servers_changed,
					 volume_monitor_daemon,
					 NULL,
					 NULL);
								       
	
	update_fstab_drives (volume_monitor_daemon);
	update_mtab_volumes (volume_monitor_daemon);
	update_connected_servers (volume_monitor_daemon);
}

void
gnome_vfs_volume_monitor_daemon_force_probe (GnomeVFSVolumeMonitor *volume_monitor)
{
	GnomeVFSVolumeMonitorDaemon *volume_monitor_daemon;
	
	volume_monitor_daemon = GNOME_VFS_VOLUME_MONITOR_DAEMON (volume_monitor);
	
#ifdef USE_HAL
	if (!dont_use_hald)
		_gnome_vfs_hal_mounts_force_reprobe (volume_monitor_daemon);
#endif
	update_fstab_drives (volume_monitor_daemon);
	update_mtab_volumes (volume_monitor_daemon);
	update_connected_servers (volume_monitor_daemon);
}

/* Remeber that this could be running on a thread other
 * than the main thread */
static void
gnome_vfs_volume_monitor_daemon_finalize (GObject *object)
{
	GnomeVFSVolumeMonitorDaemon *volume_monitor_daemon;

	volume_monitor_daemon = GNOME_VFS_VOLUME_MONITOR_DAEMON (object);
		
	_gnome_vfs_stop_monitoring_unix_mounts ();

	g_list_foreach (volume_monitor_daemon->last_mtab,
			(GFunc)_gnome_vfs_unix_mount_free, NULL);
	g_list_free (volume_monitor_daemon->last_mtab);
	
	g_list_foreach (volume_monitor_daemon->last_fstab,
			(GFunc)_gnome_vfs_unix_mount_point_free, NULL);
	g_list_free (volume_monitor_daemon->last_fstab);	

	gconf_client_notify_remove (volume_monitor_daemon->gconf_client,
				    volume_monitor_daemon->connected_id);


#ifdef USE_HAL
	if (!dont_use_hald) {
		_gnome_vfs_hal_mounts_shutdown (volume_monitor_daemon);
	}
#endif /* USE_HAL */
	
	g_object_unref (volume_monitor_daemon->gconf_client);
	
	if (G_OBJECT_CLASS (parent_class)->finalize)
		(* G_OBJECT_CLASS (parent_class)->finalize) (object);
}

static GnomeVFSDeviceType
get_device_type_from_device_and_mount (const char *device_path, const char *mount_path)
{
	const char *name;
	char *basename;
	
	if (g_str_has_prefix (device_path, "/dev/loop")) {
		return GNOME_VFS_DEVICE_TYPE_LOOPBACK;
	} else if (g_str_has_prefix (device_path, "/dev/vn")) {
	    	return GNOME_VFS_DEVICE_TYPE_LOOPBACK;
	} else if (g_str_has_prefix (device_path, "/vol/dev/diskette/") ||
		   g_str_has_prefix (device_path, "/dev/fd") ||
		   g_str_has_prefix (device_path, "/dev/floppy")) {
		return GNOME_VFS_DEVICE_TYPE_FLOPPY;
	} else if (g_str_has_prefix (device_path, "/dev/cdrom")) {
		return GNOME_VFS_DEVICE_TYPE_CDROM;
	} else if (g_str_has_prefix (device_path, "/dev/acd")) {
	    	return GNOME_VFS_DEVICE_TYPE_CDROM;
	} else if (g_str_has_prefix (device_path, "/dev/cd")) {
	    	return GNOME_VFS_DEVICE_TYPE_CDROM;
	} else if (g_str_has_prefix (device_path, "/vol/")) {
			name = mount_path + strlen ("/");

			if (g_str_has_prefix (name, "cdrom")) {
				return GNOME_VFS_DEVICE_TYPE_CDROM;
			} else if (g_str_has_prefix (name, "floppy")) {
				return GNOME_VFS_DEVICE_TYPE_FLOPPY;
			} else if (g_str_has_prefix (device_path, "/vol/dev/diskette/")) {
				return GNOME_VFS_DEVICE_TYPE_FLOPPY;
			} else if (g_str_has_prefix (name, "rmdisk")) {
				return GNOME_VFS_DEVICE_TYPE_ZIP;
			} else if (g_str_has_prefix (name, "jaz")) {
				return GNOME_VFS_DEVICE_TYPE_JAZ;
			} else if (g_str_has_prefix (name, "camera")) {
				return GNOME_VFS_DEVICE_TYPE_CAMERA;
			} else if (g_str_has_prefix (name, "memstick")) {
				return GNOME_VFS_DEVICE_TYPE_MEMORY_STICK;
			}
	} else {
		basename = g_path_get_basename (mount_path);

		if (g_str_has_prefix (basename, "cdrom") ||
		    g_str_has_prefix (basename, "cdwriter") ||
		    g_str_has_prefix (basename, "burn") ||
		    g_str_has_prefix (basename, "cdr") ||
		    g_str_has_prefix (basename, "cdrw") ||
		    g_str_has_prefix (basename, "dvdrom") ||
		    g_str_has_prefix (basename, "dvdram") ||
		    g_str_has_prefix (basename, "dvdr") ||
		    g_str_has_prefix (basename, "dvdrw") ||
		    g_str_has_prefix (basename, "cdrom_dvdrom") ||
		    g_str_has_prefix (basename, "cdrom_dvdram") ||
		    g_str_has_prefix (basename, "cdrom_dvdr") ||
		    g_str_has_prefix (basename, "cdrom_dvdrw") ||
		    g_str_has_prefix (basename, "cdr_dvdrom") ||
		    g_str_has_prefix (basename, "cdr_dvdram") ||
		    g_str_has_prefix (basename, "cdr_dvdr") ||
		    g_str_has_prefix (basename, "cdr_dvdrw") ||
		    g_str_has_prefix (basename, "cdrw_dvdrom") ||
		    g_str_has_prefix (basename, "cdrw_dvdram") ||
		    g_str_has_prefix (basename, "cdrw_dvdr") ||
		    g_str_has_prefix (basename, "cdrw_dvdrw")) {
			g_free (basename);
			return GNOME_VFS_DEVICE_TYPE_CDROM;
		} else if (g_str_has_prefix (basename, "floppy")) {
			g_free (basename);
			return GNOME_VFS_DEVICE_TYPE_FLOPPY;
		} else if (g_str_has_prefix (basename, "zip")) {
			g_free (basename);
			return GNOME_VFS_DEVICE_TYPE_ZIP;
		} else if (g_str_has_prefix (basename, "jaz")) {
			g_free (basename);
			return GNOME_VFS_DEVICE_TYPE_JAZ;
		} else if (g_str_has_prefix (basename, "camera")) {
			g_free (basename);
			return GNOME_VFS_DEVICE_TYPE_CAMERA;
		} else if (g_str_has_prefix (basename, "memstick") ||
			   g_str_has_prefix (basename, "compact_flash") ||
			   g_str_has_prefix (basename, "memory_stick") ||
			   g_str_has_prefix (basename, "smart_media") ||
			   g_str_has_prefix (basename, "sd_mmc") ||
			   g_str_has_prefix (basename, "ram")) {
			g_free (basename);
			return GNOME_VFS_DEVICE_TYPE_MEMORY_STICK;
		} else if (g_str_has_prefix (basename, "ipod")) {
			g_free (basename);
			return GNOME_VFS_DEVICE_TYPE_MUSIC_PLAYER;
		} 
		g_free (basename);
	}
	
	return GNOME_VFS_DEVICE_TYPE_HARDDRIVE;
}

static char *
make_utf8 (const char *str)
{
	GString *string;
	const char *remainder, *invalid;
	int remaining_bytes, valid_bytes;
	char *utf8;
	
	if (g_utf8_validate (str, -1, NULL)) {
		return g_strdup (str);
	}
	
	utf8 = g_locale_to_utf8 (str, -1, NULL, NULL, NULL);
	if (utf8 != NULL) {
		return utf8;
	}

	string = NULL;
	remainder = str;
	remaining_bytes = strlen (str);

	while (remaining_bytes != 0) {
		if (g_utf8_validate (remainder, remaining_bytes, &invalid)) {
			break;
		}
		valid_bytes = invalid - remainder;

		if (string == NULL) {
			string = g_string_sized_new (remaining_bytes);
		}
		g_string_append_len (string, remainder, valid_bytes);
		g_string_append_c (string, '?');

		remaining_bytes -= valid_bytes + 1;
		remainder = invalid + 1;
	}

	if (string == NULL) {
		return g_strdup (str);
	}

	g_string_append (string, remainder);
	g_string_append (string, _(" (invalid Unicode)"));
	g_assert (g_utf8_validate (string->str, -1, NULL));

	return g_string_free (string, FALSE);
}

static char *
get_drive_icon_from_type (GnomeVFSDeviceType type, const char *mount_path)
{
	char *icon_name;

	icon_name = "gnome-dev-removable";
	switch (type) {
	case GNOME_VFS_DEVICE_TYPE_NFS:
		icon_name = "gnome-fs-nfs";
		break;
		
	case GNOME_VFS_DEVICE_TYPE_SMB:
		icon_name = "gnome-fs-smb";
		break;

	case GNOME_VFS_DEVICE_TYPE_HARDDRIVE:
		if (mount_path != NULL) {
			char *basename;	
			basename = g_path_get_basename (mount_path);
			if (g_str_has_prefix (basename, 
					      "usbdisk")) {
				icon_name = "gnome-dev-removable-usb";
			} else if (g_str_has_prefix (basename, 
						     "ieee1394disk")) {
				icon_name = "gnome-dev-removable-1394";
			}
			g_free (basename);
		}
		break;

	default:
		break;
	}
	
	return g_strdup (icon_name);
}

static char *
get_icon_from_type (GnomeVFSDeviceType type, const char *mount_path)
{
	char *icon_name;
	char *basename;	

	if (mount_path != NULL) {
		basename = g_path_get_basename (mount_path);
	} else {
		basename = NULL;
	}

	icon_name = "gnome-dev-harddisk";
	switch (type) {
	case GNOME_VFS_DEVICE_TYPE_HARDDRIVE:
		if (basename != NULL) {
			if (g_str_has_prefix (basename, 
					      "usbdisk")) {
				icon_name = "gnome-dev-harddisk-usb";
			} else if (g_str_has_prefix (basename, 
						     "ieee1394disk")) {
				icon_name = "gnome-dev-harddisk-1394";
			}
		}
		break;

	case GNOME_VFS_DEVICE_TYPE_AUDIO_CD:
		icon_name = "gnome-dev-cdrom-audio";
		break;
		
	case GNOME_VFS_DEVICE_TYPE_CDROM:
		icon_name = "gnome-dev-cdrom";
		break;

	case GNOME_VFS_DEVICE_TYPE_FLOPPY:
		icon_name = "gnome-dev-floppy";
		break;

	case GNOME_VFS_DEVICE_TYPE_JAZ:
		icon_name = "gnome-dev-jazdisk";
		break;
	case GNOME_VFS_DEVICE_TYPE_ZIP:
		icon_name = "gnome-dev-zipdisk";
		break;

	case GNOME_VFS_DEVICE_TYPE_MEMORY_STICK:
		icon_name = "gnome-dev-memory";
		if (basename != NULL) {
			if (g_str_has_prefix (basename, 
					      "compact_flash")) {
				icon_name = "gnome-dev-media-cf";
			} else if (g_str_has_prefix (basename, 
						     "memory_stick")) {
				icon_name = "gnome-dev-media-ms";
			} else if (g_str_has_prefix (basename, 
						     "smart_media")) {
				icon_name = "gnome-dev-media-sm";
			} else if (g_str_has_prefix (basename, 
						     "sd_mmc")) {
				icon_name = "gnome-dev-media-sdmmc";
			}
		}
		break;
	
	case GNOME_VFS_DEVICE_TYPE_NFS:
		icon_name = "gnome-fs-nfs";
		break;

	case GNOME_VFS_DEVICE_TYPE_SMB:
		icon_name = "gnome-fs-smb";
		break;

	default:
		break;
	}

	if (basename != NULL) {
		g_free (basename);
	}

	return g_strdup (icon_name);
}


static void
diff_sorted_lists (GList *list1, GList *list2, GCompareFunc compare,
		   GList **added, GList **removed)
{
	int order;
	
	*added = *removed = NULL;

	while (list1 != NULL &&
	       list2 != NULL) {
		order = (*compare) (list1->data, list2->data);
		if (order < 0) {
			*removed = g_list_prepend (*removed, list1->data);
			list1 = list1->next;
		} else if (order > 0) {
			*added = g_list_prepend (*added, list2->data);
			list2 = list2->next;
		} else { /* same item */
			list1 = list1->next;
			list2 = list2->next;
		}
	}

	while (list1 != NULL) {
		*removed = g_list_prepend (*removed, list1->data);
		list1 = list1->next;
	}
	while (list2 != NULL) {
		*added = g_list_prepend (*added, list2->data);
		list2 = list2->next;
	}
}

/************************* fstab drives ***********************************/

static void
fixup_mountpoint (GnomeVFSUnixMountPoint *mount)
{
	/* needed for solaris */
	if (g_str_has_prefix (mount->device_path, "/vol/") &&
	    g_str_has_prefix (mount->mount_path, "/rmdisk") &&
	    g_str_has_suffix (mount->device_path, ":c")) {
		mount->device_path[strlen (mount->device_path) - 2] = 0;
	}
}


static char *
get_drive_name (GnomeVFSVolumeMonitor *volume_monitor,
		GnomeVFSDrive *drive,
		GnomeVFSUnixMountPoint *mount)
{
	char *name, *utf8_name, *rest, *end;
	int i, index;
	struct { char *machine; char *readable; } readable_table[] = {
		{ "floppy", N_("Floppy") },
		{ "cdrom", N_("CD-ROM") },

		{ "cdr",    N_("CD-R") },
		{ "cdrw",   N_("CD-RW") },
		{ "dvdrom", N_("DVD-ROM") },
		{ "dvdram", N_("DVD-RAM") },
		{ "dvdr",   N_("DVD-R") },
		{ "dvdrw",  N_("DVD-RW") },
		{ "cdrom_dvdrom", N_("CD-ROM/DVD-ROM") },
		{ "cdrom_dvdram", N_("CD-ROM/DVD-RAM") },
		{ "cdrom_dvdr",   N_("CD-ROM/DVD-R") },
		{ "cdrom_dvdrw",  N_("CD-ROM/DVD-RW") },
		{ "cdr_dvdrom", N_("CD-R/DVD-ROM") },
		{ "cdr_dvdram", N_("CD-R/DVD-RAM") },
		{ "cdr_dvdr",   N_("CD-R/DVD-R") },
		{ "cdr_dvdrw",  N_("CD-R/DVD-RW") },
		{ "cdrw_dvdrom", N_("CD-RW/DVD-ROM") },
		{ "cdrw_dvdram", N_("CD-RW/DVD-RAM") },
		{ "cdrw_dvdr",   N_("CD-RW/DVD-R") },
		{ "cdrw_dvdrw",  N_("CD-RW/DVD-RW") },

		{ "idedisk", N_("Disk") },
		{ "usbdisk", N_("USB Drive") },
		{ "ieee1394disk", N_("IEEE1394 Drive") },
		{ "compact_flash", N_("CF") },
		{ "sd_mmc", N_("SD/MMC") },
		{ "memory_stick", N_("Memory Stick") },
		{ "smart_media", N_("Smart Media") },

		{ "zip", N_("Zip Drive") },
		{ "memstick", N_("Memory Stick") },
		{ "camera", N_("Camera") },
		{ "dvd", N_("DVD") },
	};
	
	name = g_path_get_basename (mount->mount_path);

	for (i = 0; i < G_N_ELEMENTS (readable_table); i++) {
		if (g_str_has_prefix (name, readable_table[i].machine)) {
			rest = name + strlen (readable_table[i].machine);
			if (rest[0] == 0) {
				g_free (name);
				name = g_strdup (_(readable_table[i].readable));
			} else {
				index = strtol(rest, &end, 10);
				if (*end == 0) {
					g_free (name);
					name = g_strdup_printf ("%s %d", _(readable_table[i].readable), index + 1);
				}
			}
		}
	}
	
	if (strcmp (name, "floppy") == 0) {
		g_free (name);
		name = g_strdup (_("Floppy"));
	} else if (strcmp (name, "cdrom") == 0) {
		g_free (name);
		name = g_strdup (_("CD-ROM"));
	} else if (strcmp (name, "zip") == 0) {
		g_free (name);
		name = g_strdup (_("Zip Drive"));
	}

	utf8_name = make_utf8 (name);
	g_free (name);
	
	name = _gnome_vfs_volume_monitor_uniquify_drive_name (volume_monitor, utf8_name);
	g_free (utf8_name);

	return name;
}


static GnomeVFSDrive *
create_drive_from_mount_point (GnomeVFSVolumeMonitor *volume_monitor,
			       GnomeVFSUnixMountPoint *mount)
{
	GnomeVFSDrive *drive;
	GnomeVFSVolume *mounted_volume;
	char *name, *uri;
	
	if (mount->is_loopback ||
	    !(mount->is_user_mountable ||
	      g_str_has_prefix (mount->device_path, "/vol/"))) {
		return NULL;
	}

	fixup_mountpoint (mount);
	
	drive = g_object_new (GNOME_VFS_TYPE_DRIVE, NULL);

	if (strcmp(mount->filesystem_type, "supermount") == 0 &&
	    mount->dev_opt != NULL) {
		drive->priv->device_path = g_strdup (mount->dev_opt);
	} else {
		drive->priv->device_path = g_strdup (mount->device_path);
	}
	drive->priv->activation_uri = gnome_vfs_get_uri_from_local_path (mount->mount_path);
	drive->priv->is_connected = 1;

	drive->priv->device_type = get_device_type_from_device_and_mount (mount->device_path, mount->mount_path);
	if ((strcmp (mount->filesystem_type, "iso9660") == 0) ||
	    (strcmp (mount->filesystem_type, "cd9660") == 0) ||
	    (strcmp (mount->filesystem_type, "hsfs") == 0)) {
		if (drive->priv->device_type == GNOME_VFS_DEVICE_TYPE_UNKNOWN) {
			drive->priv->device_type = GNOME_VFS_DEVICE_TYPE_CDROM;
		}
	} else if (strcmp (mount->filesystem_type, "cdrfs") == 0) {
		/* AIX uses this fstype */
		if (drive->priv->device_type == GNOME_VFS_DEVICE_TYPE_UNKNOWN) {
			drive->priv->device_type = GNOME_VFS_DEVICE_TYPE_CDROM;
		}
	} else if ((strcmp (mount->filesystem_type, "hfs") == 0) ||
		   (strcmp (mount->filesystem_type, "hfsplus") == 0)) {
		if (drive->priv->device_type == GNOME_VFS_DEVICE_TYPE_UNKNOWN) {
			drive->priv->device_type = GNOME_VFS_DEVICE_TYPE_APPLE;
		}
	} else if ((strcmp (mount->filesystem_type, "vfat") == 0) ||
		   (strcmp (mount->filesystem_type, "fat") == 0)	||
		   (strcmp (mount->filesystem_type, "ntfs") == 0) ||
		   (strcmp (mount->filesystem_type, "msdos") == 0) ||
		   (strcmp (mount->filesystem_type, "msdosfs") == 0)) {
		if (drive->priv->device_type == GNOME_VFS_DEVICE_TYPE_UNKNOWN) {
			drive->priv->device_type = GNOME_VFS_DEVICE_TYPE_WINDOWS;
		}
	} else if ((strcmp (mount->filesystem_type, "smbfs") == 0) ||
		   (strcmp (mount->filesystem_type, "cifs") == 0)) {
		drive->priv->device_type = GNOME_VFS_DEVICE_TYPE_SMB;
	} else if (mount->is_loopback) {
		drive->priv->device_type = GNOME_VFS_DEVICE_TYPE_LOOPBACK;
	}

	drive->priv->icon = get_drive_icon_from_type (drive->priv->device_type, mount->mount_path);

	drive->priv->display_name = get_drive_name (volume_monitor, drive, mount);

	name = g_utf8_casefold (drive->priv->display_name, -1);
	drive->priv->display_name_key = g_utf8_collate_key (name, -1);
	g_free (name);
	
	drive->priv->is_user_visible = TRUE;
	drive->priv->volumes = NULL;

	uri = gnome_vfs_get_uri_from_local_path (mount->mount_path);
	mounted_volume = _gnome_vfs_volume_monitor_find_mtab_volume_by_activation_uri (volume_monitor, uri);
	g_free (uri);

	if (mounted_volume != NULL &&
	    mounted_volume->priv->drive == NULL) {
		gnome_vfs_drive_add_mounted_volume_private (drive, mounted_volume);
		gnome_vfs_volume_set_drive_private (mounted_volume, drive);
	}

	return drive;
}

static void
update_fstab_drives (GnomeVFSVolumeMonitorDaemon *volume_monitor_daemon)
{
	GList *new_fstab;
	GList *removed, *added;
	GList *l;
	GnomeVFSUnixMountPoint *mount;
	char *uri;
	GnomeVFSDrive *drive;
	GnomeVFSVolumeMonitor *volume_monitor;

	volume_monitor = GNOME_VFS_VOLUME_MONITOR (volume_monitor_daemon);

	if (_gnome_vfs_get_unix_mount_table (&new_fstab)) {
		new_fstab = g_list_sort (new_fstab, (GCompareFunc) _gnome_vfs_unix_mount_point_compare);
		
		diff_sorted_lists (volume_monitor_daemon->last_fstab,
				   new_fstab, (GCompareFunc) _gnome_vfs_unix_mount_point_compare,
				   &added, &removed);

		for (l = removed; l != NULL; l = l->next) {
			mount = l->data;
			uri = gnome_vfs_get_uri_from_local_path (mount->mount_path);

			drive = _gnome_vfs_volume_monitor_find_fstab_drive_by_activation_uri (volume_monitor, uri);

			if (drive != NULL) {
#ifdef USE_HAL
				/* don't remove if managed by the hal backend */
				if (dont_use_hald || drive->priv->hal_udi == NULL)
					_gnome_vfs_volume_monitor_disconnected (volume_monitor, drive);
#else
				_gnome_vfs_volume_monitor_disconnected (volume_monitor, drive);
#endif
			} else {
				g_warning ("removed drive not in old fstab list??");
			}

			g_free (uri);
		}
		
		for (l = added; l != NULL; l = l->next) {
			mount = l->data;
		
			drive = create_drive_from_mount_point (volume_monitor, mount);

			if (drive != NULL) {

#ifdef USE_HAL
				if (!dont_use_hald) {
					GnomeVFSDrive *new_drive;

					new_drive = _gnome_vfs_hal_mounts_modify_drive (volume_monitor_daemon, drive);
					if (new_drive != NULL) {
						_gnome_vfs_volume_monitor_connected (volume_monitor, new_drive);
						gnome_vfs_drive_unref (new_drive);
					}
				} else {
					_gnome_vfs_volume_monitor_connected (volume_monitor, drive);
					gnome_vfs_drive_unref (drive);
				}
#else
				_gnome_vfs_volume_monitor_connected (volume_monitor, drive);
				gnome_vfs_drive_unref (drive);
#endif /* USE_HAL */
					
			}
		}

		g_list_free (added);
		g_list_free (removed);
		g_list_foreach (volume_monitor_daemon->last_fstab,
				(GFunc)_gnome_vfs_unix_mount_point_free, NULL);
		g_list_free (volume_monitor_daemon->last_fstab);
		volume_monitor_daemon->last_fstab = new_fstab;
	}
}

/************************* mtab volumes ***********************************/

static void
fixup_mount (GnomeVFSUnixMount *mount)
{
	/* needed for solaris */
	if (g_str_has_prefix (mount->device_path, "/vol/") &&
	    g_str_has_prefix (mount->mount_path, "/rmdisk") &&
	    g_str_has_suffix (mount->device_path, ":c")) {
		mount->device_path[strlen (mount->device_path) - 2] = 0;
	}
}


static char *
modify_volume_name_for_display (const char *unmodified_name)
{
	int i;
	char *name;

	if (unmodified_name == NULL) {
		return NULL;
	}
	
	name = g_strdup (unmodified_name);

	/* Strip whitespace from the end of the name. */
	g_strchomp (name);

	/* The volume name may have '/' characters. We need to convert
	 * them to something that's suitable e.g. for use in the name of a
	 * link on the desktop.
	 */
	for (i = 0; name[i] != '\0'; i++) {
		if (name[i] == '/') {
			name[i] = '-';
		}
	}
	
	/* Save pretty name back into volume info */
	return name;
}

static char *
make_volume_name_from_path_and_fs (const char *mount_path, const char *fs_type)
{
	const char *name;
	
	if (mount_path[0] == '/' && mount_path[1] == '\0') {
		return g_strdup (_("Root Volume"));
	}
	
	name = strrchr (mount_path, '/');
	if (name == NULL) {
		if (fs_type == NULL) {
			return NULL;
		} else {
			return _gnome_vfs_filesystem_volume_name (fs_type);
		}
	}
	
	return modify_volume_name_for_display (name + 1);
}


static GnomeVFSVolume *
create_vol_from_mount (GnomeVFSVolumeMonitor *volume_monitor, GnomeVFSUnixMount *mount)
{
	GnomeVFSVolume *vol;
	char *display_name, *tmp_name, *utf8_name;
	int disctype;
	int fd;
	char *uri;
	GnomeVFSDrive *containing_drive;
	
	fixup_mount (mount);

	vol = g_object_new (GNOME_VFS_TYPE_VOLUME, NULL);

	vol->priv->volume_type = GNOME_VFS_VOLUME_TYPE_MOUNTPOINT;
	vol->priv->device_path = g_strdup (mount->device_path);
	vol->priv->unix_device = 0;  /* Caller must fill in.  */
	vol->priv->activation_uri = gnome_vfs_get_uri_from_local_path (mount->mount_path);
	vol->priv->filesystem_type = g_strdup (mount->filesystem_type);
	vol->priv->is_read_only = mount->is_read_only;
	vol->priv->is_mounted = 1;
	
	vol->priv->device_type = get_device_type_from_device_and_mount (mount->device_path, mount->mount_path);
	
	if ((strcmp (mount->filesystem_type, "iso9660") == 0) ||
	    (strcmp (mount->filesystem_type, "cd9660") == 0)) {
		if (vol->priv->device_type == GNOME_VFS_DEVICE_TYPE_UNKNOWN) {
			vol->priv->device_type = GNOME_VFS_DEVICE_TYPE_CDROM;
		}
	} else if (strcmp (mount->filesystem_type, "nfs") == 0) {
		if (strstr (vol->priv->device_path, "(pid") != NULL) {
			vol->priv->device_type = GNOME_VFS_DEVICE_TYPE_AUTOFS;
		} else {
			vol->priv->device_type = GNOME_VFS_DEVICE_TYPE_NFS;
		}
	} else if (strcmp (mount->filesystem_type, "autofs") == 0) {
		vol->priv->device_type = GNOME_VFS_DEVICE_TYPE_AUTOFS;
	} else if ((strcmp (mount->filesystem_type, "hfs") == 0)  ||
		   (strcmp (mount->filesystem_type, "hfsplus") == 0)) {
		if (vol->priv->device_type == GNOME_VFS_DEVICE_TYPE_UNKNOWN) {
			vol->priv->device_type = GNOME_VFS_DEVICE_TYPE_APPLE;
		}
	} else if ((strcmp (mount->filesystem_type, "vfat") == 0) ||
		   (strcmp (mount->filesystem_type, "fat") == 0)	||
		   (strcmp (mount->filesystem_type, "ntfs") == 0) ||
		   (strcmp (mount->filesystem_type, "msdos") == 0) ||
		   (strcmp (mount->filesystem_type, "msdosfs") == 0)) {
		if (vol->priv->device_type == GNOME_VFS_DEVICE_TYPE_UNKNOWN) {
			vol->priv->device_type = GNOME_VFS_DEVICE_TYPE_WINDOWS;
		}
	}

	display_name = NULL;
	
	/* We default to harddrive if otherwise unknown */
	if (vol->priv->device_type == GNOME_VFS_DEVICE_TYPE_UNKNOWN) {
		vol->priv->device_type = GNOME_VFS_DEVICE_TYPE_HARDDRIVE;
	}

	if (vol->priv->device_type == GNOME_VFS_DEVICE_TYPE_CDROM) {
		fd = 0;
		disctype = _gnome_vfs_get_cdrom_type (mount->device_path, &fd);

		switch (disctype) {
		case CDS_AUDIO:
			display_name = g_strdup (_("Audio CD"));
			vol->priv->device_type = GNOME_VFS_DEVICE_TYPE_AUDIO_CD;
			break;
			
		case CDS_DATA_1:
		case CDS_DATA_2:
		case CDS_XA_2_1:
		case CDS_XA_2_2:
		case CDS_MIXED:
			/* Get volume name */
			if (fd > 0) {
				tmp_name = _gnome_vfs_get_iso9660_volume_name (fd);
				display_name = modify_volume_name_for_display (tmp_name);
				g_free (tmp_name);
			}
			break;
			
		default:
			break;
		}
		
		if (fd > 0) {
			close (fd);
		}
	}

	if (display_name == NULL) {
		display_name = make_volume_name_from_path_and_fs (mount->mount_path, mount->filesystem_type);
	}

	if (display_name == NULL ||
	    display_name[0] == 0) {
		g_free (display_name);
		display_name = g_strdup (_("Unknown volume"));
	}
	
	utf8_name = make_utf8 (display_name);
	vol->priv->display_name = _gnome_vfs_volume_monitor_uniquify_volume_name (volume_monitor, utf8_name);
	g_free (display_name);
	g_free (utf8_name);

	display_name = g_utf8_casefold (vol->priv->display_name, -1);
	vol->priv->display_name_key = g_utf8_collate_key (display_name, -1);
	g_free (display_name);

	vol->priv->icon = get_icon_from_type (vol->priv->device_type, mount->mount_path);

	vol->priv->is_user_visible = 0;
	switch (vol->priv->device_type) {
	case GNOME_VFS_DEVICE_TYPE_CDROM:
	case GNOME_VFS_DEVICE_TYPE_FLOPPY:
	case GNOME_VFS_DEVICE_TYPE_ZIP:
	case GNOME_VFS_DEVICE_TYPE_JAZ:
	case GNOME_VFS_DEVICE_TYPE_CAMERA:
	case GNOME_VFS_DEVICE_TYPE_MEMORY_STICK:
		vol->priv->is_user_visible = 1;
		break;
	default:
		break;
	}
	
	vol->priv->drive = NULL;

	uri = gnome_vfs_get_uri_from_local_path (mount->mount_path);
	containing_drive = _gnome_vfs_volume_monitor_find_fstab_drive_by_activation_uri (volume_monitor, uri);
	g_free (uri);

	if (containing_drive != NULL &&
	    g_list_find (containing_drive->priv->volumes, vol) == NULL) {
		/* Make sure the mounted volume for a visible drive is visible */
		if (containing_drive->priv->is_user_visible) {
			vol->priv->is_user_visible = 1;
		}
		
		vol->priv->drive = containing_drive;
		gnome_vfs_drive_add_mounted_volume_private (containing_drive, vol);
	}

	return vol;
}

static void
update_mtab_volumes (GnomeVFSVolumeMonitorDaemon *volume_monitor_daemon)
{
	GList *new_mtab;
	GList *removed, *added, *devices;
	GList *l, *ld;
	char *uri;
	GnomeVFSVolume *vol;
	GnomeVFSVolumeMonitor *volume_monitor;

	volume_monitor = GNOME_VFS_VOLUME_MONITOR (volume_monitor_daemon);
	
	if (_gnome_vfs_get_current_unix_mounts (&new_mtab)) {
		new_mtab = g_list_sort (new_mtab, (GCompareFunc) _gnome_vfs_unix_mount_compare);
		
		diff_sorted_lists (volume_monitor_daemon->last_mtab,
				   new_mtab, (GCompareFunc) _gnome_vfs_unix_mount_compare,
				   &added, &removed);

		for (l = removed; l != NULL; l = l->next) {
			GnomeVFSUnixMount *mount = l->data;
			uri = gnome_vfs_get_uri_from_local_path (mount->mount_path);

			vol = _gnome_vfs_volume_monitor_find_mtab_volume_by_activation_uri (volume_monitor, uri);

			if (vol != NULL) {
#ifdef USE_HAL
				/* don't remove if managed by the hal backend */
				if (dont_use_hald || vol->priv->hal_udi == NULL)
					_gnome_vfs_volume_monitor_unmounted (volume_monitor, vol);
#else
				_gnome_vfs_volume_monitor_unmounted (volume_monitor, vol);
#endif
			} else {
				g_warning ("removed volume not in old mtab list??");
			}

			g_free (uri);
		}

		devices = _gnome_vfs_unix_mount_get_unix_device (added);

		for (l = added, ld = devices;
		     l != NULL;
		     l = l->next, ld = ld->next) {
			GnomeVFSUnixMount *mount = l->data;
			dev_t unix_device = GPOINTER_TO_UINT (ld->data);
		
			vol = create_vol_from_mount (volume_monitor, mount);
			vol->priv->unix_device = unix_device;
#ifdef USE_HAL
			if (!dont_use_hald) {
				GnomeVFSVolume *new_vol;

				new_vol = _gnome_vfs_hal_mounts_modify_volume (volume_monitor_daemon, vol);
				if (new_vol != NULL) {
					_gnome_vfs_volume_monitor_mounted (volume_monitor, new_vol);
					gnome_vfs_volume_unref (new_vol);
				}
			} else {
				_gnome_vfs_volume_monitor_mounted (volume_monitor, vol);			
				gnome_vfs_volume_unref (vol);
			}
#else
			_gnome_vfs_volume_monitor_mounted (volume_monitor, vol);			
			gnome_vfs_volume_unref (vol);
#endif /* USE_HAL */
		}

		g_list_free (devices);
		g_list_free (added);
		g_list_free (removed);
		g_list_foreach (volume_monitor_daemon->last_mtab,
				(GFunc)_gnome_vfs_unix_mount_free, NULL);
		g_list_free (volume_monitor_daemon->last_mtab);
		volume_monitor_daemon->last_mtab = new_mtab;
	}
}

/************************* connected server ***********************************/

static void
connected_server_free (GnomeVFSConnectedServer *server)
{
	g_free (server->id);
	g_free (server->uri);
	g_free (server->display_name);
	g_free (server->display_name_key);
	g_free (server->icon);
	g_free (server);
}

static int
connected_server_compare (GnomeVFSConnectedServer *a,
			  GnomeVFSConnectedServer *b)
{
	int res;

	res = strcmp (a->id, b->id);
	if (res != 0) {
		return res;
	}
	res = strcmp (a->uri, b->uri);
	if (res != 0) {
		return res;
	}
	res = strcmp (a->display_name_key, b->display_name_key);
	if (res != 0) {
		return res;
	}
	res = strcmp (a->icon, b->icon);
	if (res != 0) {
		return res;
	}
	return 0;
}



static GList *
get_connected_servers (GnomeVFSVolumeMonitorDaemon *volume_monitor_daemon)
{
	GSList *dirs, *l;
	char *dir;
	GnomeVFSConnectedServer *server;
	char *key, *id;
	GList *servers;

	servers = NULL;
	
	dirs = gconf_client_all_dirs (volume_monitor_daemon->gconf_client,
				      CONNECTED_SERVERS_DIR, NULL);

	for (l = dirs; l != NULL; l = l->next) {
		dir = l->data;

		id = strrchr (dir, '/');
		if (id != NULL && id[1] != 0) {
			server = g_new0 (GnomeVFSConnectedServer, 1);

			server->id = g_strdup (id+1);
			
			key = g_strconcat (dir, "/uri", NULL);
			server->uri = gconf_client_get_string (volume_monitor_daemon->gconf_client,
							       key, NULL);
			g_free (key);
			key = g_strconcat (dir, "/display_name", NULL);
			server->display_name = gconf_client_get_string (volume_monitor_daemon->gconf_client,
									key, NULL);
			g_free (key);
			key = g_strconcat (dir, "/icon", NULL);
			server->icon = gconf_client_get_string (volume_monitor_daemon->gconf_client,
								key, NULL);
			g_free (key);

			if (server->uri == NULL) {
				g_free (server->id);
				g_free (server->display_name);
				g_free (server->icon);
				g_free (server);
			} else {
				char *name;

				if (server->display_name == NULL) {
					server->display_name = g_strdup (_("Network server"));
				}

				name = g_utf8_casefold (server->display_name, -1);
				server->display_name_key = g_utf8_collate_key (name, -1);
				g_free (name);

				if (server->icon == NULL) {
					server->icon = g_strdup ("gnome-fs-share");
				}

				servers = g_list_prepend (servers, server);
			}
		}
		
		g_free (dir);
	}
	g_slist_free (dirs);
	
	return servers;
}


static GnomeVFSVolume *
create_volume_from_connected_server (GnomeVFSVolumeMonitor *volume_monitor,
				     GnomeVFSConnectedServer *server)
{
	GnomeVFSVolume *vol;
	char *name;
	
	vol = g_object_new (GNOME_VFS_TYPE_VOLUME, NULL);

	vol->priv->volume_type = GNOME_VFS_VOLUME_TYPE_CONNECTED_SERVER;
	vol->priv->activation_uri = g_strdup (server->uri);
	vol->priv->display_name = _gnome_vfs_volume_monitor_uniquify_volume_name (volume_monitor, server->display_name);

	name = g_utf8_casefold (vol->priv->display_name, -1);
	vol->priv->display_name_key = g_utf8_collate_key (name, -1);
	g_free (name);

	vol->priv->icon = g_strdup (server->icon);
	vol->priv->gconf_id = g_strdup (server->id);
	vol->priv->is_mounted = 1;
	vol->priv->is_user_visible = 1;

	return vol;
}


static void
update_connected_servers (GnomeVFSVolumeMonitorDaemon *volume_monitor_daemon)
{
	GList *new_servers;
	GnomeVFSVolumeMonitor *volume_monitor;
	GList *removed, *added;
	GnomeVFSConnectedServer *server;
	GnomeVFSVolume *volume;
	GList *l;

	volume_monitor = GNOME_VFS_VOLUME_MONITOR (volume_monitor_daemon);

	new_servers = get_connected_servers (volume_monitor_daemon);
	new_servers = g_list_sort (new_servers, (GCompareFunc) connected_server_compare);
	diff_sorted_lists (volume_monitor_daemon->last_connected_servers,
			   new_servers, (GCompareFunc) connected_server_compare,
			   &added, &removed);

	for (l = removed; l != NULL; l = l->next) {
		server = l->data;
		volume = _gnome_vfs_volume_monitor_find_connected_server_by_gconf_id (volume_monitor, server->id);
		
		if (volume != NULL) {
			_gnome_vfs_volume_monitor_unmounted (volume_monitor, volume);
		}
	}
	
	for (l = added; l != NULL; l = l->next) {
		server = l->data;
		
		volume = create_volume_from_connected_server (volume_monitor, server);
		_gnome_vfs_volume_monitor_mounted (volume_monitor, volume);
		gnome_vfs_volume_unref (volume);
	}
	
	g_list_free (added);
	g_list_free (removed);
	g_list_foreach (volume_monitor_daemon->last_connected_servers,
			(GFunc)connected_server_free, NULL);
	g_list_free (volume_monitor_daemon->last_connected_servers);
	volume_monitor_daemon->last_connected_servers = new_servers;
}

