static void
impl_dispose (GObject *object)
{
	RBGenericPlayerSourcePrivate *priv = GENERIC_PLAYER_SOURCE_GET_PRIVATE (object);

	if (priv->load_playlists_id != 0) {
		g_source_remove (priv->load_playlists_id);
		priv->load_playlists_id = 0;
	}

	if (priv->db != NULL) {
		if (priv->ignore_type != RHYTHMDB_ENTRY_TYPE_INVALID) {
			rhythmdb_entry_delete_by_type (priv->db, priv->ignore_type);
			g_boxed_free (RHYTHMDB_TYPE_ENTRY_TYPE, priv->ignore_type);
			priv->ignore_type = RHYTHMDB_ENTRY_TYPE_INVALID;
		}
		if (priv->error_type != RHYTHMDB_ENTRY_TYPE_INVALID) {
			rhythmdb_entry_delete_by_type (priv->db, priv->error_type);
			g_boxed_free (RHYTHMDB_TYPE_ENTRY_TYPE, priv->error_type);
			priv->error_type = RHYTHMDB_ENTRY_TYPE_INVALID;
		}

		g_object_unref (priv->db);
		priv->db = NULL;
	}

	if (priv->import_job != NULL) {
		rhythmdb_import_job_cancel (priv->import_job);
		g_object_unref (priv->import_job);
		priv->import_job = NULL;
	}

	G_OBJECT_CLASS (rb_generic_player_source_parent_class)->dispose (object);
}
static gboolean
impl_can_delete (RBSource *source)
{
	RBGenericPlayerSourcePrivate *priv = GENERIC_PLAYER_SOURCE_GET_PRIVATE (source);

	return (priv->read_only == FALSE);
}
static void
impl_finalize (GObject *object)
{
	RBGenericPlayerSourcePrivate *priv;

	g_return_if_fail (RB_IS_GENERIC_PLAYER_SOURCE (object));
	priv = GENERIC_PLAYER_SOURCE_GET_PRIVATE (object);
}
char *
rb_generic_player_source_get_playlist_path (RBGenericPlayerSource *source)
{
	RBGenericPlayerSourcePrivate *priv = GENERIC_PLAYER_SOURCE_GET_PRIVATE (source);
	char *path;

	g_object_get (priv->device_info, "playlist-path", &path, NULL);
	return path;
}
void
rb_generic_player_source_trash_or_delete_entries (RBGenericPlayerSource *source,
						  GList *entries,
						  gboolean _delete)
{
	RBGenericPlayerSourcePrivate *priv = GENERIC_PLAYER_SOURCE_GET_PRIVATE (source);
	GList *tem;

	if (priv->read_only != FALSE)
		return;

	for (tem = entries; tem != NULL; tem = tem->next) {
		RhythmDBEntry *entry;
		const char *uri;
		GFile *file;
		GFile *dir;

		entry = tem->data;
		uri = rhythmdb_entry_get_string (entry, RHYTHMDB_PROP_LOCATION);
		file = g_file_new_for_uri (uri);
		if (_delete)
			g_file_delete (file, NULL, NULL);
		else
			g_file_trash (file, NULL, NULL);

		/* now walk up the directory structure and delete empty dirs
		 * until we reach the root or one of the device's audio folders.
		 */
		dir = g_file_get_parent (file);
		while (can_delete_directory (source, dir)) {
			GFile *parent;
			char *path;

			path = g_file_get_path (dir);
			rb_debug ("trying to delete %s", path);
			g_free (path);

			if (g_file_delete (dir, NULL, NULL) == FALSE) {
				break;
			}

			parent = g_file_get_parent (dir);
			if (parent == NULL) {
				break;
			}
			g_object_unref (dir);
			dir = parent;
		}

		g_object_unref (dir);
		g_object_unref (file);

		rhythmdb_entry_delete (priv->db, entry);
	}

	rhythmdb_commit (priv->db);
}
static GObject *
impl_constructor (GType type,
		  guint n_construct_properties,
		  GObjectConstructParam *construct_properties)
{
	RBGenericPlayerSource *source;
	RBGenericPlayerSourcePrivate *priv;
	GMount *mount;
	char *mount_name;
	RBShell *shell;
	GFile *root;
	GFileInfo *info;
	GError *error = NULL;

	source = RB_GENERIC_PLAYER_SOURCE (G_OBJECT_CLASS (rb_generic_player_source_parent_class)->
					   constructor (type, n_construct_properties, construct_properties));

	priv = GENERIC_PLAYER_SOURCE_GET_PRIVATE (source);

	g_object_get (source, "shell", &shell, NULL);

	g_object_get (shell, "db", &priv->db, NULL);
	
	priv->import_errors = rb_import_errors_source_new (shell, priv->error_type);

	g_object_unref (shell);

	g_object_get (source, "mount", &mount, NULL);

	root = g_mount_get_root (mount);
	mount_name = g_mount_get_name (mount);

	info = g_file_query_filesystem_info (root, G_FILE_ATTRIBUTE_FILESYSTEM_READONLY, NULL, &error);
	if (error != NULL) {
		rb_debug ("error querying filesystem info for %s: %s", mount_name, error->message);
		g_error_free (error);
		priv->read_only = FALSE;
	} else {
		priv->read_only = g_file_info_get_attribute_boolean (info, G_FILE_ATTRIBUTE_FILESYSTEM_READONLY);
		g_object_unref (info);
	}

	g_free (mount_name);
	g_object_unref (root);
	g_object_unref (mount);

	priv->folder_depth = -1;	/* 0 is a possible value, I guess */
	priv->playlist_format_unknown = TRUE;

	get_device_info (source);

	load_songs (source);

	return G_OBJECT (source);
}
static void
debug_device_info (RBGenericPlayerSource *source, GMount *mount, const char *what)
{
	char *dbg;
	char *path;
	RBGenericPlayerSourcePrivate *priv = GENERIC_PLAYER_SOURCE_GET_PRIVATE (source);
	GVolume *volume;

	volume = g_mount_get_volume (mount);
	path = g_volume_get_identifier (volume, G_VOLUME_IDENTIFIER_KIND_UNIX_DEVICE);
	rb_debug ("device information for %s from %s", path, what);
	g_free (path);
	g_object_unref (volume);

	if (priv->audio_folders != NULL) {
		dbg = g_strjoinv (", ", priv->audio_folders);
		rb_debug ("audio folders: %s", dbg);
		g_free (dbg);
	} else {
		rb_debug ("no audio folders set");
	}

	if (priv->output_mime_types != NULL) {
		dbg = g_strjoinv (", ", priv->output_mime_types);
		rb_debug ("output types: %s", dbg);
		g_free (dbg);
	} else {
		rb_debug ("no output types set");
	}

	if (priv->playlist_format_unknown) {
		rb_debug ("playlist format is unknown");
	} else {
		if (priv->playlist_format_m3u)
			rb_debug ("M3U playlist format is supported");
		if (priv->playlist_format_pls)
			rb_debug ("PLS playlist format is supported");
		if (priv->playlist_format_iriver_pla)
			rb_debug ("iRiver PLA playlist format is supported");
	}

	if (priv->playlist_path != NULL) {
		rb_debug ("playlist path: %s", priv->playlist_path);
	} else {
		rb_debug ("no playlist path is set");
	}

	if (priv->folder_depth == -1) {
		rb_debug ("audio folder depth is not set");
	} else {
		rb_debug ("audio folder depth: %d", priv->folder_depth);
	}
}
static void
playlist_deleted_cb (RBSource *playlist, RBGenericPlayerSource *source)
{
	RBGenericPlayerSourcePrivate *priv = GENERIC_PLAYER_SOURCE_GET_PRIVATE (source);
	GList *p;

	p = g_list_find (priv->playlists, playlist);
	if (p != NULL) {
		priv->playlists = g_list_delete_link (priv->playlists, p);
		g_object_unref (playlist);
	}
}
static void
default_load_playlists (RBGenericPlayerSource *source)
{
	RBGenericPlayerSourcePrivate *priv = GENERIC_PLAYER_SOURCE_GET_PRIVATE (source);
	char *mount_path;
	char *playlist_path;
	char *full_playlist_path;
	char **playlist_formats;

	mount_path = rb_generic_player_source_get_mount_path (source);

	g_object_get (priv->device_info, "playlist-path", &playlist_path, NULL);
	if (playlist_path) {

		/* If the device only supports a single playlist, just load that */
		if (g_str_has_suffix (playlist_path, ".m3u") ||
		    g_str_has_suffix (playlist_path, ".pls")) {
			full_playlist_path = rb_uri_append_path (mount_path, playlist_path);
			if (rb_uri_exists (full_playlist_path)) {
				load_playlist_file (source, full_playlist_path, playlist_path);
			}

			g_free (full_playlist_path);
			g_free (playlist_path);
			return;
		}

		/* Otherwise, limit the search to the device's playlist folder */
		if (g_str_has_suffix (playlist_path, "/%File")) {
			playlist_path[strlen (playlist_path) - strlen("/%File")] = '\0';
		}
		full_playlist_path = rb_uri_append_path (mount_path, playlist_path);
		rb_debug ("constructed playlist search path %s", full_playlist_path);
	} else {
		full_playlist_path = g_strdup (mount_path);
	}

	/* only try to load playlists if the device has at least one playlist format */
	g_object_get (priv->device_info, "playlist-formats", &playlist_formats, NULL);
	if (playlist_formats != NULL && g_strv_length (playlist_formats) > 0) {
		rb_debug ("searching for playlists in %s", playlist_path);
		rb_uri_handle_recursively (full_playlist_path,
					   NULL,
					   (RBUriRecurseFunc) visit_playlist_dirs,
					   source);
	}
	g_strfreev (playlist_formats);

	g_free (playlist_path);
	g_free (full_playlist_path);
	g_free (mount_path);
}
static void
impl_finalize (GObject *object)
{
	RBGenericPlayerSourcePrivate *priv;

	g_return_if_fail (RB_IS_GENERIC_PLAYER_SOURCE (object));
	priv = GENERIC_PLAYER_SOURCE_GET_PRIVATE (object);

	g_free (priv->mount_path);
	g_strfreev (priv->audio_folders);
	g_strfreev (priv->output_mime_types);
	g_free (priv->playlist_path);
}
void
rb_generic_player_source_add_playlist (RBGenericPlayerSource *source,
				       RBShell *shell,
				       RBSource *playlist)
{
	RBGenericPlayerSourcePrivate *priv = GENERIC_PLAYER_SOURCE_GET_PRIVATE (source);
	g_object_ref (playlist);
	priv->playlists = g_list_prepend (priv->playlists, playlist);

	g_signal_connect_object (playlist, "deleted", G_CALLBACK (playlist_deleted_cb), source, 0);

	rb_shell_append_source (shell, playlist, RB_SOURCE (source));
}
static gboolean
visit_playlist_dirs (GFile *file,
		     gboolean dir,
		     RBGenericPlayerSource *source)
{
	char *basename;
	char *uri;
	RhythmDBEntry *entry;
	RhythmDBEntryType entry_type;
	RBGenericPlayerSourcePrivate *priv = GENERIC_PLAYER_SOURCE_GET_PRIVATE (source);

	if (dir) {
		return TRUE;
	}

	/* check if we've already got an entry 
	 * for this file, just to save some i/o.
	 */
	uri = g_file_get_uri (file);
	entry = rhythmdb_entry_lookup_by_location (priv->db, uri);
	g_free (uri);
	if (entry != NULL) {
		gboolean is_song;

		is_song = FALSE;

		g_object_get (source, "entry-type", &entry_type, NULL);
		is_song = (rhythmdb_entry_get_entry_type (entry) == entry_type);
		g_boxed_free (RHYTHMDB_TYPE_ENTRY_TYPE, entry_type);

		if (is_song) {
			rb_debug ("%s was loaded as a song",
				  rhythmdb_entry_get_string (entry, RHYTHMDB_PROP_LOCATION));
			return TRUE;
		}
	}

	basename = g_file_get_basename (file);
	if (strcmp (basename, ".is_audio_player") != 0) {
		char *playlist_path;
		playlist_path = g_file_get_path (file);
		load_playlist_file (source, playlist_path, basename);
		g_free (playlist_path);
	}

	g_free (basename);

	return TRUE;
}
static GList *
impl_get_mime_types (RBRemovableMediaSource *source)
{
	RBGenericPlayerSourcePrivate *priv = GENERIC_PLAYER_SOURCE_GET_PRIVATE (source);
	GList *list = NULL;
	char **output_formats;
	char **mime;

	g_object_get (priv->device_info, "output-formats", &output_formats, NULL);
	for (mime = output_formats; mime && *mime != NULL; mime++) {
		list = g_list_prepend (list, g_strdup (*mime));
	}
	g_strfreev (output_formats);
	return g_list_reverse (list);
}
static void
impl_get_property (GObject *object, guint prop_id, GValue *value, GParamSpec *pspec)
{
	RBGenericPlayerSourcePrivate *priv = GENERIC_PLAYER_SOURCE_GET_PRIVATE (object);

	switch (prop_id) {
	case PROP_IGNORE_ENTRY_TYPE:
		g_value_set_boxed (value, priv->ignore_type);
		break;
	case PROP_ERROR_ENTRY_TYPE:
		g_value_set_boxed (value, priv->error_type);
		break;
	default:
		G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
		break;
	}
}
static gboolean
can_delete_directory (RBGenericPlayerSource *source, GFile *dir)
{
	RBGenericPlayerSourcePrivate *priv = GENERIC_PLAYER_SOURCE_GET_PRIVATE (source);
	gboolean result;
	GMount *mount;
	GFile *root;
	char **audio_folders;
	int i;

	g_object_get (source, "mount", &mount, NULL);
	root = g_mount_get_root (mount);
	g_object_unref (mount);

	/* can't delete the root dir */
	if (g_file_equal (dir, root)) {
		rb_debug ("refusing to delete device root dir");
		g_object_unref (root);
		return FALSE;
	}

	/* can't delete the device's audio folders */
	result = TRUE;
	g_object_get (priv->device_info, "audio-folders", &audio_folders, NULL);
	if (audio_folders != NULL && g_strv_length (audio_folders) > 0) {
		for (i = 0; audio_folders[i] != NULL; i++) {
			GFile *check;

			check = g_file_resolve_relative_path (root, audio_folders[i]);
			if (g_file_equal (dir, check)) {
				rb_debug ("refusing to delete device audio folder %s", audio_folders[i]);
				result = FALSE;
			}
			g_object_unref (check);
		}
	}
	g_strfreev (audio_folders);

	/* can delete anything else */
	g_object_unref (root);
	return result;
}
static gboolean
impl_can_move_to_trash (RBSource *source)
{
	RBGenericPlayerSourcePrivate *priv = GENERIC_PLAYER_SOURCE_GET_PRIVATE (source);
	RBEntryView *view;
	GList *sel, *tem;
	gboolean ret;

	if (priv->read_only != FALSE)
		return FALSE;

	ret = FALSE;

	view = rb_source_get_entry_view (source);
	sel = rb_entry_view_get_selected_entries (view);

	for (tem = sel; tem != NULL; tem = tem->next) {
		RhythmDBEntry *entry;
		const char *uri;
		GFile *file;
		GFileInfo *info;

		entry = tem->data;
		uri = rhythmdb_entry_get_string (entry, RHYTHMDB_PROP_LOCATION);
		file = g_file_new_for_uri (uri);
		info = g_file_query_info (file, G_FILE_ATTRIBUTE_ACCESS_CAN_TRASH, G_FILE_QUERY_INFO_NONE, NULL, NULL);
		g_object_unref (file);
		if (info == NULL) {
			ret = FALSE;
			break;
		}
		ret = g_file_info_get_attribute_boolean (info, G_FILE_ATTRIBUTE_ACCESS_CAN_TRASH);
		g_object_unref (info);
		if (ret == FALSE)
			break;
	}

	g_list_foreach (sel, (GFunc)rhythmdb_entry_unref, NULL);
	g_list_free (sel);

	return ret;
}
void
rb_generic_player_source_set_supported_formats (RBGenericPlayerSource *source, TotemPlParser *parser)
{
	RBGenericPlayerSourcePrivate *priv = GENERIC_PLAYER_SOURCE_GET_PRIVATE (source);
	char **playlist_formats;
	const char *check[] = { "audio/x-mpegurl", "audio/x-scpls", "audio/x-iriver-pla" };

	g_object_get (priv->device_info, "playlist-formats", &playlist_formats, NULL);
	if (playlist_formats != NULL && g_strv_length (playlist_formats) > 0) {
		int i;
		for (i = 0; i < G_N_ELEMENTS (check); i++) {
			if (strv_contains (playlist_formats, check[i])) {
				totem_pl_parser_add_ignored_mimetype (parser, check[i]);
			}
		}
	}
	g_strfreev (playlist_formats);

	totem_pl_parser_add_ignored_mimetype (parser, "x-directory/normal");
}
static void
impl_get_status (RBSource *source, char **text, char **progress_text, float *progress)
{
	RBGenericPlayerSourcePrivate *priv = GENERIC_PLAYER_SOURCE_GET_PRIVATE (source);

	/* get default status text first */
	RB_SOURCE_CLASS (rb_generic_player_source_parent_class)->impl_get_status (source, text, progress_text, progress);

	/* override with bits of import status */
	if (priv->import_job != NULL) {
		int total;
		int imported;

		total = rhythmdb_import_job_get_total (priv->import_job);
		imported = rhythmdb_import_job_get_imported (priv->import_job);

		g_free (*progress_text);
		*progress_text = g_strdup_printf (_("Importing (%d/%d)"), imported, total);
		*progress = ((float)imported / (float)total);
	}
}
static void
set_playlist_path (RBGenericPlayerSource *source, const char *path)
{
	RBGenericPlayerSourcePrivate *priv = GENERIC_PLAYER_SOURCE_GET_PRIVATE (source);

	g_free (priv->playlist_path);

	/*
	 * The HAL spec allows the use of a '%File' variable to substitute
	 * the playlist name.  All current examples are of the form 'playlists/%File',
	 * so we'll just make that work.
	 */
	if (g_str_has_suffix (path, "/%File")) {

		priv->playlist_path = g_strdup (path);
		priv->playlist_path[strlen (path) - strlen("/%File")] = '\0';
	} else {
		priv->playlist_path = g_strdup (path);
	}
	rb_debug ("playlist path set to %s", priv->playlist_path);
}
gboolean
rb_generic_player_source_can_trash_entries (RBGenericPlayerSource *source,
					    GList *entries)
{
	RBGenericPlayerSourcePrivate *priv = GENERIC_PLAYER_SOURCE_GET_PRIVATE (source);
	GList *tem;
	gboolean ret;

	if (priv->read_only != FALSE)
		return FALSE;

	ret = FALSE;

	for (tem = entries; tem != NULL; tem = tem->next) {
		RhythmDBEntry *entry;
		const char *uri;
		GFile *file;
		GFileInfo *info;

		entry = tem->data;
		uri = rhythmdb_entry_get_string (entry, RHYTHMDB_PROP_LOCATION);
		file = g_file_new_for_uri (uri);
		info = g_file_query_info (file,
					  G_FILE_ATTRIBUTE_ACCESS_CAN_TRASH,
					  G_FILE_QUERY_INFO_NONE,
					  NULL,
					  NULL);
		g_object_unref (file);
		if (info == NULL) {
			ret = FALSE;
			break;
		}
		ret = g_file_info_get_attribute_boolean (info, G_FILE_ATTRIBUTE_ACCESS_CAN_TRASH);
		g_object_unref (info);
		if (ret == FALSE)
			break;
	}

	return ret;
}
static void
load_songs (RBGenericPlayerSource *source)
{
	RBGenericPlayerSourcePrivate *priv = GENERIC_PLAYER_SOURCE_GET_PRIVATE (source);
	RhythmDBEntryType entry_type;
	char **audio_folders;
	char *mount_path;

	mount_path = rb_generic_player_source_get_mount_path (source);
	g_object_get (source, "entry-type", &entry_type, NULL);

	/* if we have a set of folders on the device containing audio files,
	 * load only those folders, otherwise add the whole volume.
	 */
	priv->import_job = rhythmdb_import_job_new (priv->db, entry_type, priv->ignore_type, priv->error_type);

	g_signal_connect_object (priv->import_job, "complete", G_CALLBACK (import_complete_cb), source, 0);
	g_signal_connect_object (priv->import_job, "status-changed", G_CALLBACK (import_status_changed_cb), source, 0);

	g_object_get (priv->device_info, "audio-folders", &audio_folders, NULL);
	if (audio_folders != NULL && g_strv_length (audio_folders) > 0) {
		int af;
		for (af=0; audio_folders[af] != NULL; af++) {
			char *path;
			path = rb_uri_append_path (mount_path, audio_folders[af]);
			rb_debug ("loading songs from device audio folder %s", path);
			rhythmdb_import_job_add_uri (priv->import_job, path);
			g_free (path);
		}
	} else {
		rb_debug ("loading songs from device mount path %s", mount_path);
		rhythmdb_import_job_add_uri (priv->import_job, mount_path);
	}
	g_strfreev (audio_folders);

	rhythmdb_import_job_start (priv->import_job);

	g_boxed_free (RHYTHMDB_TYPE_ENTRY_TYPE, entry_type);
	g_free (mount_path);
}
static char *
default_get_mount_path (RBGenericPlayerSource *source)
{
	RBGenericPlayerSourcePrivate *priv = GENERIC_PLAYER_SOURCE_GET_PRIVATE (source);

	if (priv->mount_path == NULL) {
		GMount *mount;
		GFile *root;

		g_object_get (source, "mount", &mount, NULL);

		root = g_mount_get_root (mount);
		if (root != NULL) {
			priv->mount_path = g_file_get_uri (root);
			g_object_unref (root);
		}

		g_object_unref (mount);
	}

	return g_strdup (priv->mount_path);
}
static void
set_playlist_formats (RBGenericPlayerSource *source, char **formats)
{
	RBGenericPlayerSourcePrivate *priv = GENERIC_PLAYER_SOURCE_GET_PRIVATE (source);
	RhythmDBEntryType entry_type;
	int fmt;

	priv->playlist_format_unknown = TRUE;
	priv->playlist_format_m3u = FALSE;
	priv->playlist_format_pls = FALSE;
	priv->playlist_format_iriver_pla = FALSE;
	for (fmt = 0; formats[fmt] != NULL; fmt++) {
		char *format;
		char *stripped;

		format = g_strdup (formats[fmt]);
		stripped = g_strstrip (format);

		if (strcmp (stripped, "audio/x-mpegurl") == 0) {
			priv->playlist_format_unknown = FALSE;
			priv->playlist_format_m3u = TRUE;
		} else if (strcmp (stripped, "audio/x-scpls") == 0) {
			priv->playlist_format_unknown = FALSE;
			priv->playlist_format_pls = TRUE;
		} else if (strcmp (stripped, "audio/x-iriver-pla") == 0) {
			priv->playlist_format_unknown = FALSE;
			priv->playlist_format_iriver_pla = TRUE;
		} else {
			rb_debug ("unrecognized playlist format: %s", stripped);
		}

		g_free (format);
	}

	g_object_get (source, "entry-type", &entry_type, NULL);
	entry_type->has_playlists = (priv->playlist_format_unknown == FALSE);
	g_boxed_free (RHYTHMDB_TYPE_ENTRY_TYPE, entry_type);
}
static void
import_complete_cb (RhythmDBImportJob *job, int total, RBGenericPlayerSource *source)
{
	RBGenericPlayerSourceClass *klass = RB_GENERIC_PLAYER_SOURCE_GET_CLASS (source);
	RBGenericPlayerSourcePrivate *priv = GENERIC_PLAYER_SOURCE_GET_PRIVATE (source);
	RBShell *shell;

	GDK_THREADS_ENTER ();
	
	g_object_get (source, "shell", &shell, NULL);
	rb_shell_append_source (shell, priv->import_errors, RB_SOURCE (source));
	g_object_unref (shell);

	if (klass->impl_load_playlists)
		klass->impl_load_playlists (source);

	g_object_unref (priv->import_job);
	priv->import_job = NULL;
	
	rb_source_notify_status_changed (RB_SOURCE (source));

	GDK_THREADS_LEAVE ();
}
TotemPlParserType
rb_generic_player_source_get_playlist_format (RBGenericPlayerSource *source)
{
	RBGenericPlayerSourcePrivate *priv = GENERIC_PLAYER_SOURCE_GET_PRIVATE (source);
	TotemPlParserType result;
	char **playlist_formats;

	g_object_get (priv->device_info, "playlist-formats", &playlist_formats, NULL);

	if (playlist_formats == NULL || g_strv_length (playlist_formats) == 0 || strv_contains (playlist_formats, "audio/x-scpls")) {
		result = TOTEM_PL_PARSER_PLS;
	} else if (strv_contains (playlist_formats, "audio/x-mpegurl")) {
		result = TOTEM_PL_PARSER_M3U;
	} else if (strv_contains (playlist_formats, "audio/x-iriver-pla")) {
		result = TOTEM_PL_PARSER_IRIVER_PLA;
	} else {
		/* now what? */
		result = TOTEM_PL_PARSER_PLS;
	}

	g_strfreev (playlist_formats);
	return result;
}
static void
default_load_playlists (RBGenericPlayerSource *source)
{
	RBGenericPlayerSourcePrivate *priv = GENERIC_PLAYER_SOURCE_GET_PRIVATE (source);
	char *mount_path;
	char *playlist_path = NULL;

	mount_path = rb_generic_player_source_get_mount_path (source);
	if (priv->playlist_path) {

		/* If the device only supports a single playlist, just load that */
		if (g_str_has_suffix (priv->playlist_path, ".m3u") ||
		    g_str_has_suffix (priv->playlist_path, ".pls")) {
			char *playlist_path = rb_uri_append_path (mount_path, priv->playlist_path);
			if (rb_uri_exists (playlist_path)) {
				load_playlist_file (source, playlist_path, priv->playlist_path);
			}

			return;
		}

		/* Otherwise, limit the search to the device's playlist folder.
		 * The optional trailing '/%File' is stripped in set_playlist_path
		 */
		playlist_path = rb_uri_append_path (mount_path, priv->playlist_path);
		rb_debug ("constructed playlist search path %s", playlist_path);

	}

	rb_uri_handle_recursively (playlist_path ? playlist_path : mount_path,
				   NULL,
				   (RBUriRecurseFunc) visit_playlist_dirs,
				   source);

	g_free (playlist_path);
	g_free (mount_path);
}
static void
impl_delete_thyself (RBSource *source)
{
	GList *pl;
	GList *p;
	RBGenericPlayerSourcePrivate *priv = GENERIC_PLAYER_SOURCE_GET_PRIVATE (source);

	/* take a copy of the list first, as playlist_deleted_cb modifies priv->playlists */
	pl = g_list_copy (priv->playlists);
	for (p = pl; p != NULL; p = p->next) {
		RBSource *playlist = RB_SOURCE (p->data);
		rb_source_delete_thyself (playlist);
	}
	g_list_free (priv->playlists);
	g_list_free (pl);
	priv->playlists = NULL;

	if (priv->import_errors != NULL) {
		rb_source_delete_thyself (priv->import_errors);
		priv->import_errors = NULL;
	}

	RB_SOURCE_CLASS (rb_generic_player_source_parent_class)->impl_delete_thyself (source);
}
static GObject *
impl_constructor (GType type,
		  guint n_construct_properties,
		  GObjectConstructParam *construct_properties)
{
	RBGenericPlayerSource *source;
	RBGenericPlayerSourcePrivate *priv;
	GMount *mount;
	char **playlist_formats;
	char *mount_name;
	RBShell *shell;
	GFile *root;
	GFileInfo *info;
	GError *error = NULL;

	source = RB_GENERIC_PLAYER_SOURCE (G_OBJECT_CLASS (rb_generic_player_source_parent_class)->
					   constructor (type, n_construct_properties, construct_properties));

	priv = GENERIC_PLAYER_SOURCE_GET_PRIVATE (source);

	g_object_get (source, "shell", &shell, NULL);

	g_object_get (shell, "db", &priv->db, NULL);
	
	priv->import_errors = rb_import_errors_source_new (shell, priv->error_type);

	g_object_unref (shell);

	g_object_get (source, "mount", &mount, NULL);

	root = g_mount_get_root (mount);
	mount_name = g_mount_get_name (mount);

	info = g_file_query_filesystem_info (root, G_FILE_ATTRIBUTE_FILESYSTEM_READONLY, NULL, &error);
	if (error != NULL) {
		rb_debug ("error querying filesystem info for %s: %s", mount_name, error->message);
		g_error_free (error);
		priv->read_only = FALSE;
	} else {
		priv->read_only = g_file_info_get_attribute_boolean (info, G_FILE_ATTRIBUTE_FILESYSTEM_READONLY);
		g_object_unref (info);
	}

	g_free (mount_name);
	g_object_unref (root);
	g_object_unref (mount);

	g_object_get (priv->device_info, "playlist-formats", &playlist_formats, NULL);
	if (playlist_formats != NULL && g_strv_length (playlist_formats) > 0) {
		RhythmDBEntryType entry_type;

		g_object_get (source, "entry-type", &entry_type, NULL);
		entry_type->has_playlists = TRUE;
		g_boxed_free (RHYTHMDB_TYPE_ENTRY_TYPE, entry_type);
	}
	g_strfreev (playlist_formats);

	load_songs (source);

	return G_OBJECT (source);
}
static void
get_device_info (RBGenericPlayerSource *source)
{
	RBGenericPlayerSourcePrivate *priv = GENERIC_PLAYER_SOURCE_GET_PRIVATE (source);
	GMount *mount;
	GFile *is_audio_player;
	GError *error = NULL;
#ifdef HAVE_HAL
	LibHalContext *ctx;
#endif
	g_object_get (source, "mount", &mount, NULL);

#ifdef HAVE_HAL
	ctx = get_hal_context ();
	if (ctx != NULL) {
		char *udi;

		udi = get_hal_udi_for_player (ctx, mount);

		if (udi != NULL) {
			DBusError error;
			char *prop;
			char **proplist;
			int value;

			/* get audio folders */
			dbus_error_init (&error);
			proplist = libhal_device_get_property_strlist (ctx, udi, "portable_audio_player.audio_folders", &error);
			if (proplist) {
				if (!dbus_error_is_set (&error)) {
					priv->audio_folders = g_strdupv (proplist);
				}
				libhal_free_string_array (proplist);
			}
			free_dbus_error ("getting audio folder list", &error);

			/* get supported mime-types */
			dbus_error_init (&error);
			proplist = libhal_device_get_property_strlist (ctx, udi, "portable_audio_player.output_formats", &error);
			if (proplist) {
				if (!dbus_error_is_set (&error)) {
					priv->output_mime_types = g_strdupv (proplist);
				}
				libhal_free_string_array (proplist);
			}
			free_dbus_error ("getting supported mime-type list", &error);

			/* get playlist format */
			dbus_error_init (&error);
			proplist = libhal_device_get_property_strlist (ctx, udi, "portable_audio_player.playlist_format", &error);
			if (proplist) {
				if (!dbus_error_is_set (&error)) {
					set_playlist_formats (source, proplist);
				}
				libhal_free_string_array (proplist);
			}
			free_dbus_error ("getting playlist format", &error);

			/* get playlist path - in current hal-info packages, this is sometimes a string
			 * and sometimes a strlist, so try both. 
			 * http://bugs.freedesktop.org/show_bug.cgi?id=19961
			 */
			dbus_error_init (&error);
			proplist = libhal_device_get_property_strlist (ctx, udi, "portable_audio_player.playlist_path", &error);
			if (proplist && !dbus_error_is_set (&error)) {
				set_playlist_path (source, proplist[0]);
				libhal_free_string_array (proplist);
			}
			free_dbus_error ("getting playlist path (strlist)", &error);
			
			dbus_error_init (&error);
			prop = libhal_device_get_property_string (ctx, udi, "portable_audio_player.playlist_path", &error);
			if (prop && !dbus_error_is_set (&error)) {
				set_playlist_path (source, prop);
				libhal_free_string (prop);
			}
			free_dbus_error ("getting playlist path (string)", &error);

			/* get max folder depth */
			dbus_error_init (&error);
			value = libhal_device_get_property_int (ctx, udi, "portable_audio_player.folder_depth", &error);
			if (!dbus_error_is_set (&error)) {
				priv->folder_depth = value;
			}
			free_dbus_error ("getting max folder depth", &error);

			debug_device_info (source, mount, "HAL");
		} else {
			rb_debug ("no player info available (HAL doesn't recognise it as a player");
		}
		g_free (udi);
	}
	cleanup_hal_context (ctx);
#endif

	/* allow HAL info to be overridden with .is_audio_player file */
	is_audio_player = get_is_audio_player_file (mount);
	if (is_audio_player != NULL) {
		char *data = NULL;
		gsize data_size = 0;

		rb_debug ("reading .is_audio_player file");

		g_file_load_contents (is_audio_player, NULL, &data, &data_size, NULL, &error);
		if (error != NULL) {
			/* can we sensibly report this anywhere? */
			rb_debug ("error reading .is_audio_player file: %s", error->message);
			g_clear_error (&error);
		} else {
			GKeyFile *keyfile;
			GError *error = NULL;
			char *munged;
			gsize munged_size;
			const char *fake_group = "[x-rb-data]\n";
			char *group;

			/* prepend a group name to the file contents */
			munged_size = data_size + strlen (fake_group);
			munged = g_malloc0 (munged_size + 1);
			strcpy (munged, fake_group);
			memcpy (munged + strlen (fake_group), data, data_size);

			keyfile = g_key_file_new ();
			g_key_file_set_list_separator (keyfile, ',');
			if (g_key_file_load_from_data (keyfile, munged, munged_size, G_KEY_FILE_NONE, &error) == FALSE) {
				/* and this */
				rb_debug ("error loading .is_audio_player file: %s", error->message);
				g_error_free (error);
			} else {
				char *value;
				char **list;

				group = g_key_file_get_start_group (keyfile);
				
				list = g_key_file_get_string_list (keyfile, group, "audio_folders", NULL, NULL);
				if (list != NULL) {
					g_strfreev (priv->audio_folders);
					priv->audio_folders = list;
				}

				list = g_key_file_get_string_list (keyfile, group, "output_formats", NULL, NULL);
				if (list != NULL) {
					g_strfreev (priv->output_mime_types);
					priv->output_mime_types = list;
				}
				
				list = g_key_file_get_string_list (keyfile, group, "playlist_format", NULL, NULL);
				if (list != NULL) {
					set_playlist_formats (source, list);
					g_strfreev (list);
				}

				value = g_key_file_get_string (keyfile, group, "playlist_path", NULL);
				if (value != NULL) {
					set_playlist_path (source, value);
					g_free (value);
				}

				if (g_key_file_has_key (keyfile, group, "folder_depth", NULL)) {
					priv->folder_depth = g_key_file_get_integer (keyfile, group, "folder_depth", NULL);
				}
				g_free (group);
			}

			g_key_file_free (keyfile);
			g_free (munged);
			
			debug_device_info (source, mount, ".is_audio_player file");
		}
		g_free (data);

		g_object_unref (is_audio_player);
	} else {
		rb_debug ("no .is_audio_player file found on this device");
	}

	g_object_unref (mount);
}
static char *
impl_build_dest_uri (RBRemovableMediaSource *source,
		     RhythmDBEntry *entry,
		     const char *mimetype,
		     const char *extension)
{
	RBGenericPlayerSourcePrivate *priv = GENERIC_PLAYER_SOURCE_GET_PRIVATE (source);
	char *artist, *album, *title;
	gulong track_number, disc_number;
	const char *folders;
	char **audio_folders;
	char *mount_path;
	char *number;
	char *file = NULL;
	char *path;
	char *ext;

	rb_debug ("building dest uri for entry at %s", rhythmdb_entry_get_string (entry, RHYTHMDB_PROP_LOCATION));

	if (extension != NULL) {
		ext = g_strconcat (".", extension, NULL);
	} else {
		ext = g_strdup ("");
	}

	artist = sanitize_path (rhythmdb_entry_get_string (entry, RHYTHMDB_PROP_ARTIST));
	album = sanitize_path (rhythmdb_entry_get_string (entry, RHYTHMDB_PROP_ALBUM));
	title = sanitize_path (rhythmdb_entry_get_string (entry, RHYTHMDB_PROP_TITLE));

	/* we really do need to fix this so untagged entries actually have NULL rather than
	 * a translated string.
	 */
	if (strcmp (artist, _("Unknown")) == 0 && strcmp (album, _("Unknown")) == 0 &&
	    g_str_has_suffix (rhythmdb_entry_get_string (entry, RHYTHMDB_PROP_LOCATION), title)) {
		/* file isn't tagged, so just use the filename as-is, replacing the extension */
		char *p;

		p = g_utf8_strrchr (title, -1, '.');
		if (p != NULL) {
			*p = '\0';
		}
		file = g_strdup_printf ("%s%s", title, ext);
	}

	if (file == NULL) {
		int folder_depth;

		track_number = rhythmdb_entry_get_ulong (entry, RHYTHMDB_PROP_TRACK_NUMBER);
		disc_number = rhythmdb_entry_get_ulong (entry, RHYTHMDB_PROP_DISC_NUMBER);
		if (disc_number > 0)
			number = g_strdup_printf ("%.02u.%.02u", (guint)disc_number, (guint)track_number);
		else
			number = g_strdup_printf ("%.02u", (guint)track_number);

		g_object_get (priv->device_info, "folder-depth", &folder_depth, NULL);
		switch (folder_depth) {
		case 0:
			/* artist - album - number - title */
			file = g_strdup_printf ("%s - %s - %s - %s%s",
						artist, album, number, title, ext);
			break;

		case 1:
			/* artist - album/number - title */
			file = g_strdup_printf ("%s - %s" G_DIR_SEPARATOR_S "%s - %s%s",
						artist, album, number, title, ext);
			break;

		default: /* use this for players that don't care */
		case 2:
			/* artist/album/number - title */
			file = g_strdup_printf ("%s" G_DIR_SEPARATOR_S "%s" G_DIR_SEPARATOR_S "%s - %s%s",
						artist, album, number, title, ext);
			break;
		}
		g_free (number);
	}

	g_free (artist);
	g_free (album);
	g_free (title);
	g_free (ext);

	if (file == NULL)
		return NULL;

	g_object_get (priv->device_info, "audio-folders", &audio_folders, NULL);
	if (audio_folders != NULL && g_strv_length (audio_folders) > 0) {
		folders = g_strdup (audio_folders[0]);
	} else {
		folders = "";
	}
	g_strfreev (audio_folders);

	mount_path = rb_generic_player_source_get_mount_path (RB_GENERIC_PLAYER_SOURCE (source));
	path = g_build_filename (mount_path, folders, file, NULL);
	g_free (file);
	g_free (mount_path);

	/* TODO: check for duplicates, or just overwrite by default? */
	rb_debug ("dest file is %s", path);
	return path;
}