static char *
default_uri_to_playlist_uri (RBGenericPlayerSource *source, const char *uri, TotemPlParserType playlist_type)
{
	char *mount_uri;
	char *playlist_uri;

	switch (playlist_type) {
	case TOTEM_PL_PARSER_IRIVER_PLA:
		/* we need absolute paths within the device filesystem for this format */
		mount_uri = rb_generic_player_source_get_mount_path (source);
		if (g_str_has_prefix (uri, mount_uri) == FALSE) {
			rb_debug ("uri %s is not under device mount uri %s", uri, mount_uri);
			return NULL;
		}

		playlist_uri = g_strdup_printf ("file://%s", uri + strlen (mount_uri));
		return playlist_uri;

	case TOTEM_PL_PARSER_M3U_DOS:
	case TOTEM_PL_PARSER_M3U:
	case TOTEM_PL_PARSER_PLS:
	default:
		/* leave the URI as-is, so we end up with relative paths in the playlist file */
		return g_strdup (uri);
	}
}
static void
load_playlist_file (RBGenericPlayerSource *source,
		    const char *playlist_path,
		    const char *rel_path)
{
	RhythmDBEntryType entry_type;
	RBGenericPlayerPlaylistSource *playlist;
	RBShell *shell;
	char *mount_path;

	g_object_get (source,
		      "shell", &shell,
		      "entry-type", &entry_type,
		      NULL);

	mount_path = rb_generic_player_source_get_mount_path (source);
	rb_debug ("loading playlist %s", playlist_path);
	playlist = RB_GENERIC_PLAYER_PLAYLIST_SOURCE (
			rb_generic_player_playlist_source_new (shell,
							       source,
							       playlist_path,
							       mount_path,
							       entry_type));

	if (playlist != NULL) {
		rb_generic_player_source_add_playlist (source, shell, RB_SOURCE (playlist));
	}

	g_boxed_free (RHYTHMDB_TYPE_ENTRY_TYPE, entry_type);
	g_object_unref (shell);
	g_free (mount_path);
}
static void
load_songs (RBGenericPlayerSource *source)
{
	RBGenericPlayerSourcePrivate *priv = GET_PRIVATE (source);
	RhythmDBEntryType *entry_type;
	char **audio_folders;
	char *mount_path;
	RBShell *shell;
	RBTaskList *tasklist;
	char *name;
	char *label;

	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_object_get (source, "name", &name, NULL);
	label = g_strdup_printf (_("Scanning %s"), name);
	g_object_set (priv->import_job, "task-label", label, NULL);
	g_free (label);
	g_free (name);

	g_signal_connect_object (priv->import_job, "complete", G_CALLBACK (import_complete_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_object_get (source, "shell", &shell, NULL);
	g_object_get (shell, "task-list", &tasklist, NULL);
	rb_task_list_add_task (tasklist, RB_TASK_PROGRESS (priv->import_job));
	g_object_unref (tasklist);
	g_object_unref (shell);

	g_object_unref (entry_type);
	g_free (mount_path);
}
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 char *
default_uri_to_playlist_uri (RBGenericPlayerSource *source, const char *uri)
{
	char *mount_uri;
	char *playlist_uri;

	mount_uri = rb_generic_player_source_get_mount_path (source);
	if (g_str_has_prefix (uri, mount_uri) == FALSE) {
		rb_debug ("uri %s is not under device mount uri %s", uri, mount_uri);
		return NULL;
	}

	playlist_uri = g_strdup_printf ("file://%s", uri + strlen (mount_uri));
	return playlist_uri;
}
static char *
default_uri_from_playlist_uri (RBGenericPlayerSource *source, const char *uri)
{
	char *mount_uri;
	char *full_uri;

	mount_uri = rb_generic_player_source_get_mount_path (source);
	if (g_str_has_prefix (uri, mount_uri)) {
		return g_strdup (uri);
	}

	full_uri = rb_uri_append_uri (mount_uri, uri);
	g_free (mount_uri);

	rb_debug ("%s => %s", uri, full_uri);
	return full_uri;
}
static char *
impl_uri_from_playlist_uri (RBGenericPlayerSource *source, const char *uri)
{
	const char *path;
	char *local_uri;
	char *mount_uri;

	if (!g_str_has_prefix (uri, NOKIA_INTERNAL_MOUNTPOINT)) {
		rb_debug ("found playlist uri with unexpected mountpoint");
		return NULL;
	}

	path = uri + strlen (NOKIA_INTERNAL_MOUNTPOINT);
	mount_uri = rb_generic_player_source_get_mount_path (source);
	local_uri = rb_uri_append_uri (mount_uri, path);
	g_free (mount_uri);
	return local_uri;
}
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 guint64
get_fs_property (RBGenericPlayerSource *source, const char *attr)
{
	char *mountpoint;
	GFile *root;
	GFileInfo *info;
	guint64 value = 0;

	mountpoint = rb_generic_player_source_get_mount_path (source);
	root = g_file_new_for_uri (mountpoint);
	g_free (mountpoint);

	info = g_file_query_filesystem_info (root, attr, NULL, NULL);
	g_object_unref (root);
	if (info != NULL) {
		if (g_file_info_has_attribute (info, attr)) {
			value = g_file_info_get_attribute_uint64 (info, attr);
		}
		g_object_unref (info);
	}
	return value;
}
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 gboolean
save_playlist (RBGenericPlayerPlaylistSource *source)
{
	TotemPlParser *parser;
	TotemPlParserType playlist_type;
	RhythmDBQueryModel *query_model;
	char *name;
	char *temp_path;
	GError *error = NULL;
	RBGenericPlayerPlaylistSourcePrivate *priv = GET_PRIVATE (source);
	GFile *file;
	gboolean result;
	SavePlaylistData data;

	priv->save_playlist_id = 0;
	playlist_type = rb_generic_player_source_get_playlist_format (priv->player_source);

	g_object_get (source,
		      "name", &name,
		      "base-query-model", &query_model,
		      NULL);

	/* if we don't already have a name for this playlist, make one now */
	if (priv->playlist_path == NULL) {
		char *playlist_dir;
		char *mount_uri;
		char *filename;
		const char *ext;
		GFile *dir;
		GFile *playlist;

		ext = playlist_format_extension (playlist_type);

		if (name == NULL || name[0] == '\0') {
			/* now what? */
			filename = g_strdup_printf ("unnamed%s", ext);
		} else {
			filename = g_strdup_printf ("%s%s", name, ext);
		}

		playlist_dir = rb_generic_player_source_get_playlist_path (priv->player_source);
		mount_uri = rb_generic_player_source_get_mount_path (priv->player_source);

		dir = g_file_new_for_uri (mount_uri);
		if (playlist_dir != NULL) {
			GFile *pdir;

			pdir = g_file_resolve_relative_path (dir, playlist_dir);
			g_object_unref (dir);
			dir = pdir;
		}

		playlist = g_file_resolve_relative_path (dir, filename);
		priv->playlist_path = g_file_get_path (playlist);
		
		g_free (mount_uri);
		g_free (playlist_dir);

		g_object_unref (dir);
	}

	temp_path = g_strdup_printf ("%s%06X", priv->playlist_path, g_random_int_range (0, 0xFFFFFF));
	file = g_file_new_for_path (temp_path);

	parser = totem_pl_parser_new ();
	data.source = source;
	data.playlist_type = playlist_type;
#if TOTEM_PL_PARSER_CHECK_VERSION(2,29,1)
	data.playlist = totem_pl_playlist_new ();

	gtk_tree_model_foreach (GTK_TREE_MODEL (query_model),
				(GtkTreeModelForeachFunc) save_playlist_foreach,
				&data);
	if (rb_debug_matches ("totem_pl_parser_save", "totem-pl-parser.c")) {
		g_object_set (parser, "debug", TRUE, NULL);
	}

	result = totem_pl_parser_save (parser, data.playlist, file, name, playlist_type, &error);
	g_object_unref (data.playlist);
	data.playlist = NULL;
#else
	if (rb_debug_matches ("totem_pl_parser_write_with_title", "totem-pl-parser.c")) {
		g_object_set (parser, "debug", TRUE, NULL);
	}

	result = totem_pl_parser_write_with_title (parser,
						   GTK_TREE_MODEL (query_model),
						   (TotemPlParserIterFunc) save_playlist_entry,
						   temp_path,
						   name,
						   playlist_type,
						   &data,
						   &error);
#endif
	if (result == FALSE) {
		/* XXX report this more usefully */
		g_warning ("Playlist save failed: %s", error ? error->message : "<no error>");
	} else {
		GFile *dest;

		dest = g_file_new_for_path (priv->playlist_path);
		g_file_move (file, dest, G_FILE_COPY_OVERWRITE | G_FILE_COPY_NO_FALLBACK_FOR_MOVE, NULL, NULL, NULL, &error);
		if (error != NULL) {
			/* XXX report this more usefully */
			g_warning ("moving %s => %s failed: %s", temp_path, priv->playlist_path, error->message);
		}

		g_object_unref (dest);
	}

	g_clear_error (&error);
	g_free (name);
	g_free (temp_path);
	g_object_unref (query_model);
	g_object_unref (parser);
	g_object_unref (file);

	return FALSE;
}
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;
}
static gboolean
save_playlist (RBGenericPlayerPlaylistSource *source)
{
	TotemPlParser *parser;
	TotemPlParserType playlist_type;
	RhythmDBQueryModel *query_model;
	char *name;
	char *temp_uri;
	GError *error = NULL;
	RBGenericPlayerPlaylistSourcePrivate *priv = GET_PRIVATE (source);

	priv->save_playlist_id = 0;
	playlist_type = rb_generic_player_source_get_playlist_format (priv->player_source);

	g_object_get (source,
		      "name", &name,
		      "base-query-model", &query_model,
		      NULL);

	/* if we don't already have a name for this playlist, make one now */
	if (priv->playlist_path == NULL) {
		char *playlist_dir;
		char *mount_uri;
		char *filename;
		const char *ext;
		GFile *dir;
		GFile *playlist;

		ext = playlist_format_extension (playlist_type);

		if (name == NULL || name[0] == '\0') {
			/* now what? */
			filename = g_strdup_printf ("unnamed%s", ext);
		} else {
			filename = g_strdup_printf ("%s%s", name, ext);
		}

		playlist_dir = rb_generic_player_source_get_playlist_path (priv->player_source);
		mount_uri = rb_generic_player_source_get_mount_path (priv->player_source);

		dir = g_file_new_for_uri (mount_uri);
		if (playlist_dir != NULL) {
			GFile *pdir;

			pdir = g_file_resolve_relative_path (dir, playlist_dir);
			g_object_unref (dir);
			dir = pdir;
		}

		playlist = g_file_resolve_relative_path (dir, filename);
		priv->playlist_path = g_file_get_path (playlist);
		
		g_free (mount_uri);
		g_free (playlist_dir);

		g_object_unref (dir);
	}

	temp_uri = g_strdup_printf ("%s%06X", priv->playlist_path, g_random_int_range (0, 0xFFFFFF));

	parser = totem_pl_parser_new ();
	if (rb_debug_matches ("totem_pl_parser_write_with_title", "totem-pl-parser.c")) {
		g_object_set (parser, "debug", TRUE, NULL);
	}
	if (totem_pl_parser_write_with_title (parser,
					      GTK_TREE_MODEL (query_model),
					      (TotemPlParserIterFunc) save_playlist_entry,
					      temp_uri,
					      name,
					      playlist_type,
					      source,
					      &error) == FALSE) {
		/* XXX report this more usefully */
		g_warning ("Playlist save failed: %s", error->message);
	} else {
		GFile *dest;
		GFile *src;

		dest = g_file_new_for_path (priv->playlist_path);
		src = g_file_new_for_path (temp_uri);
		g_file_move (src, dest, G_FILE_COPY_OVERWRITE | G_FILE_COPY_NO_FALLBACK_FOR_MOVE, NULL, NULL, NULL, &error);
		if (error != NULL) {
			/* XXX report this more usefully */
			g_warning ("Replacing playlist failed: %s", error->message);
		}

		g_object_unref (dest);
		g_object_unref (src);
	}

	g_clear_error (&error);
	g_free (name);
	g_free (temp_uri);
	g_object_unref (query_model);

	return FALSE;
}