hythmbox-2.98/plugins/generic-player/rb-generic-player-playlist-source.c

No issues found

  1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*-
  2  *
  3  *  Copyright (C) 2007 Jonathan Matthew  <jonathan@d14n.org>
  4  *
  5  *  This program is free software; you can redistribute it and/or modify
  6  *  it under the terms of the GNU General Public License as published by
  7  *  the Free Software Foundation; either version 2 of the License, or
  8  *  (at your option) any later version.
  9  *
 10  *  The Rhythmbox authors hereby grant permission for non-GPL compatible
 11  *  GStreamer plugins to be used and distributed together with GStreamer
 12  *  and Rhythmbox. This permission is above and beyond the permissions granted
 13  *  by the GPL license by which Rhythmbox is covered. If you modify this code
 14  *  you may extend this exception to your version of the code, but you are not
 15  *  obligated to do so. If you do not wish to do so, delete this exception
 16  *  statement from your version.
 17  *
 18  *  This program is distributed in the hope that it will be useful,
 19  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 20  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 21  *  GNU General Public License for more details.
 22  *
 23  *  You should have received a copy of the GNU General Public License
 24  *  along with this program; if not, write to the Free Software
 25  *  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301  USA.
 26  *
 27  */
 28 
 29 #include "config.h"
 30 
 31 #include <glib/gi18n.h>
 32 #include <gtk/gtk.h>
 33 #include <totem-pl-parser.h>
 34 
 35 #include "rb-generic-player-playlist-source.h"
 36 #include "rb-generic-player-source.h"
 37 #include "rb-debug.h"
 38 #include "rb-file-helpers.h"
 39 #include "rb-util.h"
 40 
 41 #define PLAYLIST_SAVE_TIMEOUT	1
 42 
 43 typedef struct
 44 {
 45 	char *playlist_path;		/* hmm, replace with GFile? */
 46 	char *device_root;
 47 	gint save_playlist_id;
 48 	RBGenericPlayerSource *player_source;
 49 	gboolean loading;
 50 } RBGenericPlayerPlaylistSourcePrivate;
 51 
 52 G_DEFINE_DYNAMIC_TYPE(RBGenericPlayerPlaylistSource,
 53 		      rb_generic_player_playlist_source,
 54 		      RB_TYPE_STATIC_PLAYLIST_SOURCE)
 55 
 56 #define GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), RB_TYPE_GENERIC_PLAYER_PLAYLIST_SOURCE, RBGenericPlayerPlaylistSourcePrivate))
 57 
 58 enum {
 59 	PROP_0,
 60 	PROP_PLAYLIST_PATH,
 61 	PROP_DEVICE_ROOT,
 62 	PROP_PLAYER_SOURCE
 63 };
 64 
 65 typedef struct {
 66 	RBGenericPlayerPlaylistSource *source;
 67 #if TOTEM_PL_PARSER_CHECK_VERSION(2,29,1)
 68 	TotemPlPlaylist *playlist;
 69 #endif
 70 	TotemPlParserType playlist_type;
 71 } SavePlaylistData;
 72 
 73 
 74 static void
 75 impl_save_to_xml (RBPlaylistSource *source, xmlNodePtr node)
 76 {
 77 	/* do nothing; just to prevent weirdness */
 78 }
 79 
 80 static void
 81 set_field_from_property (TotemPlPlaylist *playlist,
 82 			 TotemPlPlaylistIter *iter,
 83 			 RhythmDBEntry *entry,
 84 			 RhythmDBPropType property,
 85 			 const char *field)
 86 {
 87 	const char *value;
 88 
 89 	value = rhythmdb_entry_get_string (entry, property);
 90 	if (value != NULL) {
 91 		totem_pl_playlist_set (playlist, iter, field, value, NULL);
 92 	}
 93 }
 94 
 95 static gboolean
 96 save_playlist_foreach (GtkTreeModel *model,
 97 		       GtkTreePath *path,
 98 		       GtkTreeIter *iter,
 99 		       SavePlaylistData *data)
100 {
101 	RBGenericPlayerPlaylistSourcePrivate *priv = GET_PRIVATE (data->source);
102 	RhythmDBEntry *entry;
103 	TotemPlPlaylistIter pl_iter;
104 	const char *host_uri;
105 	char *uri;
106 
107 	entry = rhythmdb_query_model_iter_to_entry (RHYTHMDB_QUERY_MODEL (model), iter);
108 	if (entry == NULL) {
109 		return FALSE;
110 	}
111 
112 	host_uri = rhythmdb_entry_get_string (entry, RHYTHMDB_PROP_LOCATION);
113 	uri = rb_generic_player_source_uri_to_playlist_uri (priv->player_source, host_uri, data->playlist_type);
114 
115 	totem_pl_playlist_append (data->playlist, &pl_iter);
116 	totem_pl_playlist_set (data->playlist, &pl_iter, TOTEM_PL_PARSER_FIELD_URI, uri, NULL);
117 	set_field_from_property (data->playlist, &pl_iter, entry, RHYTHMDB_PROP_ARTIST, TOTEM_PL_PARSER_FIELD_AUTHOR);
118 	set_field_from_property (data->playlist, &pl_iter, entry, RHYTHMDB_PROP_GENRE, TOTEM_PL_PARSER_FIELD_GENRE);
119 	set_field_from_property (data->playlist, &pl_iter, entry, RHYTHMDB_PROP_ALBUM, TOTEM_PL_PARSER_FIELD_ALBUM);
120 	set_field_from_property (data->playlist, &pl_iter, entry, RHYTHMDB_PROP_TITLE, TOTEM_PL_PARSER_FIELD_TITLE);
121 	rhythmdb_entry_unref (entry);
122 
123 	g_free (uri);
124 	return FALSE;
125 }
126 
127 /* this probably belongs more in totem than here */
128 static const char *
129 playlist_format_extension (TotemPlParserType playlist_type)
130 {
131 	switch (playlist_type) {
132 	case TOTEM_PL_PARSER_PLS:
133 		return ".pls";
134 		break;
135 	case TOTEM_PL_PARSER_M3U:
136 	case TOTEM_PL_PARSER_M3U_DOS:
137 		return ".m3u";
138 		break;
139 	case TOTEM_PL_PARSER_IRIVER_PLA:
140 		return ".pla";
141 		break;
142 	case TOTEM_PL_PARSER_XSPF:
143 		return ".xspf";
144 		break;
145 	default:
146 		g_assert_not_reached ();
147 	}
148 }
149 
150 static gboolean
151 save_playlist (RBGenericPlayerPlaylistSource *source)
152 {
153 	TotemPlParser *parser;
154 	TotemPlParserType playlist_type;
155 	RhythmDBQueryModel *query_model;
156 	char *name;
157 	char *temp_path;
158 	GError *error = NULL;
159 	RBGenericPlayerPlaylistSourcePrivate *priv = GET_PRIVATE (source);
160 	GFile *file;
161 	gboolean result;
162 	SavePlaylistData data;
163 
164 	priv->save_playlist_id = 0;
165 	playlist_type = rb_generic_player_source_get_playlist_format (priv->player_source);
166 
167 	g_object_get (source,
168 		      "name", &name,
169 		      "base-query-model", &query_model,
170 		      NULL);
171 
172 	/* if we don't already have a name for this playlist, make one now */
173 	if (priv->playlist_path == NULL) {
174 		char *playlist_dir;
175 		char *mount_uri;
176 		char *filename;
177 		const char *ext;
178 		GFile *dir;
179 		GFile *playlist;
180 
181 		ext = playlist_format_extension (playlist_type);
182 
183 		if (name == NULL || name[0] == '\0') {
184 			/* now what? */
185 			filename = g_strdup_printf ("unnamed%s", ext);
186 		} else {
187 			filename = g_strdup_printf ("%s%s", name, ext);
188 		}
189 
190 		playlist_dir = rb_generic_player_source_get_playlist_path (priv->player_source);
191 		mount_uri = rb_generic_player_source_get_mount_path (priv->player_source);
192 
193 		dir = g_file_new_for_uri (mount_uri);
194 		if (playlist_dir != NULL) {
195 			GFile *pdir;
196 
197 			pdir = g_file_resolve_relative_path (dir, playlist_dir);
198 			g_object_unref (dir);
199 			dir = pdir;
200 		}
201 
202 		playlist = g_file_resolve_relative_path (dir, filename);
203 		priv->playlist_path = g_file_get_path (playlist);
204 		
205 		g_free (mount_uri);
206 		g_free (playlist_dir);
207 
208 		g_object_unref (dir);
209 	}
210 
211 	temp_path = g_strdup_printf ("%s%06X", priv->playlist_path, g_random_int_range (0, 0xFFFFFF));
212 	file = g_file_new_for_path (temp_path);
213 
214 	parser = totem_pl_parser_new ();
215 	data.source = source;
216 	data.playlist_type = playlist_type;
217 	data.playlist = totem_pl_playlist_new ();
218 
219 	gtk_tree_model_foreach (GTK_TREE_MODEL (query_model),
220 				(GtkTreeModelForeachFunc) save_playlist_foreach,
221 				&data);
222 	if (rb_debug_matches ("totem_pl_parser_save", "totem-pl-parser.c")) {
223 		g_object_set (parser, "debug", TRUE, NULL);
224 	}
225 
226 	result = totem_pl_parser_save (parser, data.playlist, file, name, playlist_type, &error);
227 	g_object_unref (data.playlist);
228 	data.playlist = NULL;
229 
230 	if (result == FALSE) {
231 		/* XXX report this more usefully */
232 		g_warning ("Playlist save failed: %s", error ? error->message : "<no error>");
233 	} else {
234 		GFile *dest;
235 
236 		dest = g_file_new_for_path (priv->playlist_path);
237 		g_file_move (file, dest, G_FILE_COPY_OVERWRITE | G_FILE_COPY_NO_FALLBACK_FOR_MOVE, NULL, NULL, NULL, &error);
238 		if (error != NULL) {
239 			/* XXX report this more usefully */
240 			g_warning ("moving %s => %s failed: %s", temp_path, priv->playlist_path, error->message);
241 		}
242 
243 		g_object_unref (dest);
244 	}
245 
246 	g_clear_error (&error);
247 	g_free (name);
248 	g_free (temp_path);
249 	g_object_unref (query_model);
250 	g_object_unref (parser);
251 	g_object_unref (file);
252 
253 	return FALSE;
254 }
255 
256 static void
257 handle_playlist_start_cb (TotemPlParser *playlist,
258 			  const char *uri,
259 			  GHashTable *metadata,
260 			  RBGenericPlayerPlaylistSource *source)
261 {
262 	const char *title;
263 	title = g_hash_table_lookup (metadata, TOTEM_PL_PARSER_FIELD_TITLE);
264 	if (title != NULL) {
265 		rb_debug ("got name '%s' for playlist", title);
266 		g_object_set (source, "name", title, NULL);
267 	}
268 }
269 
270 static void
271 handle_playlist_entry_cb (TotemPlParser *playlist,
272 			  const char *uri,
273 			  GHashTable *metadata,
274 			  RBGenericPlayerPlaylistSource *source)
275 {
276 	char *local_uri;
277 	char *name;
278 	char *canon_uri;
279 	RBGenericPlayerPlaylistSourcePrivate *priv = GET_PRIVATE (source);
280 
281 	local_uri = rb_generic_player_source_uri_from_playlist_uri (priv->player_source, uri);
282 	if (local_uri == NULL)
283 		return;
284 
285 	canon_uri = rb_canonicalise_uri (local_uri);
286 
287 	g_object_get (source, "name", &name, NULL);
288 	rb_debug ("adding '%s' as '%s' to playlist '%s' (%s)", uri, canon_uri, name, priv->playlist_path);
289 	rb_static_playlist_source_add_location (RB_STATIC_PLAYLIST_SOURCE (source), canon_uri, -1);
290 	g_free (canon_uri);
291 	g_free (local_uri);
292 	g_free (name);
293 }
294 
295 static gboolean
296 load_playlist (RBGenericPlayerPlaylistSource *source)
297 {
298 	RBGenericPlayerPlaylistSourcePrivate *priv = GET_PRIVATE (source);
299 	TotemPlParser *parser;
300 	gboolean result;
301 	GFile *file;
302 	char *name;
303 	char *uri;
304 
305 	if (priv->playlist_path == NULL) {
306 		/* this happens when we're creating a new playlist */
307 		rb_debug ("playlist has no path; obviously can't load it");
308 		g_object_set (source, "name", "", NULL);
309 		return TRUE;
310 	}
311 
312 	priv->loading = TRUE;
313 	file = g_file_new_for_path (priv->playlist_path);
314 
315 	/* make a default name for the playlist based on the filename */
316 	name = g_file_get_basename (file);
317 	g_object_set (source, "name", name, NULL);
318 	g_free (name);
319 
320 	parser = totem_pl_parser_new ();
321 	if (rb_debug_matches ("totem_pl_parser_parse", "totem-pl-parser.c")) {
322 		g_object_set (parser, "debug", TRUE, NULL);
323 	}
324 
325 	rb_generic_player_source_set_supported_formats (priv->player_source, parser);
326 	g_signal_connect (parser,
327 			  "entry-parsed", G_CALLBACK (handle_playlist_entry_cb),
328 			  source);
329 	g_signal_connect (parser,
330 			  "playlist-started", G_CALLBACK (handle_playlist_start_cb),
331 			  source);
332 	g_object_set (G_OBJECT (parser), "recurse", FALSE, NULL);
333 
334 	uri = g_file_get_uri (file);
335 	switch (totem_pl_parser_parse_with_base (parser, uri, priv->device_root, FALSE)) {
336 	case TOTEM_PL_PARSER_RESULT_SUCCESS:
337 		rb_debug ("playlist parsed successfully");
338 		result = TRUE;
339 		break;
340 
341 	case TOTEM_PL_PARSER_RESULT_ERROR:
342 		rb_debug ("playlist parser returned an error");
343 		result = FALSE;
344 		break;
345 
346 	case TOTEM_PL_PARSER_RESULT_UNHANDLED:
347 		rb_debug ("playlist parser didn't handle the file");
348 		result = FALSE;
349 		break;
350 
351 	case TOTEM_PL_PARSER_RESULT_IGNORED:
352 		rb_debug ("playlist parser ignored the file");
353 		result = FALSE;
354 		break;
355 	default:
356 		g_assert_not_reached ();
357 	}
358 	g_free (uri);
359 	g_object_unref (file);
360 
361 	priv->loading = FALSE;
362 	return result;
363 }
364 
365 static void
366 impl_mark_dirty (RBPlaylistSource *source)
367 {
368 	RBGenericPlayerPlaylistSourcePrivate *priv = GET_PRIVATE (source);
369 
370 	if (priv->loading)
371 		return;
372 
373 	/* save the playlist in a few seconds */
374 	if (priv->save_playlist_id != 0) {
375 		g_source_remove (priv->save_playlist_id);
376 	}
377 
378 	priv->save_playlist_id = g_timeout_add_seconds (PLAYLIST_SAVE_TIMEOUT,
379 							(GSourceFunc) save_playlist,
380 							source);
381 }
382 
383 RBSource *
384 rb_generic_player_playlist_source_new (RBShell *shell,
385 				       RBGenericPlayerSource *player_source,
386 				       const char *playlist_file,
387 				       const char *device_root,
388 				       RhythmDBEntryType *entry_type)
389 {
390 	RBSource *source;
391 	source = RB_SOURCE (g_object_new (RB_TYPE_GENERIC_PLAYER_PLAYLIST_SOURCE,
392 					  "shell", shell,
393 					  "is-local", FALSE,
394 					  "entry-type", entry_type,
395 					  "player-source", player_source,
396 					  "playlist-path", playlist_file,
397 					  "device-root", device_root,
398 					  NULL));
399 
400 	if (load_playlist (RB_GENERIC_PLAYER_PLAYLIST_SOURCE (source)) == FALSE) {
401 		rb_debug ("playlist didn't parse; killing the source");
402 		if (g_object_is_floating (source))
403 			g_object_ref_sink (source);
404 		g_object_unref (source);
405 		return NULL;
406 	}
407 
408 	return source;
409 }
410 
411 void
412 rb_generic_player_playlist_delete_from_player (RBGenericPlayerPlaylistSource *source)
413 {
414 	RBGenericPlayerPlaylistSourcePrivate *priv = GET_PRIVATE (source);
415 
416 	if (priv->playlist_path != NULL) {
417 		GError *error = NULL;
418 		GFile *playlist;
419 
420 		playlist = g_file_new_for_path (priv->playlist_path);
421 		g_file_delete (playlist, NULL, &error);
422 		if (error != NULL) {
423 			g_warning ("Deleting playlist %s failed: %s", priv->playlist_path, error->message);
424 			g_clear_error (&error);
425 		}
426 		g_object_unref (playlist);
427 	} else {
428 		rb_debug ("playlist was never saved: nothing to delete");
429 	}
430 }
431 
432 static void
433 rb_generic_player_playlist_source_init (RBGenericPlayerPlaylistSource *source)
434 {
435 }
436 
437 static void
438 impl_dispose (GObject *object)
439 {
440 	RBGenericPlayerPlaylistSourcePrivate *priv = GET_PRIVATE (object);
441 
442 	if (priv->save_playlist_id != 0) {
443 		g_source_remove (priv->save_playlist_id);
444 		save_playlist (RB_GENERIC_PLAYER_PLAYLIST_SOURCE (object));
445 	}
446 
447 	if (priv->player_source != NULL) {
448 		g_object_unref (priv->player_source);
449 		priv->player_source = NULL;
450 	}
451 
452 	G_OBJECT_CLASS (rb_generic_player_playlist_source_parent_class)->dispose (object);
453 }
454 
455 static void
456 impl_finalize (GObject *object)
457 {
458 	RBGenericPlayerPlaylistSourcePrivate *priv = GET_PRIVATE (object);
459 
460 	g_free (priv->playlist_path);
461 
462 	G_OBJECT_CLASS (rb_generic_player_playlist_source_parent_class)->finalize (object);
463 }
464 
465 static void
466 impl_get_property (GObject *object, guint prop_id, GValue *value, GParamSpec *pspec)
467 {
468 	RBGenericPlayerPlaylistSourcePrivate *priv = GET_PRIVATE (object);
469 
470 	switch (prop_id) {
471 	case PROP_PLAYER_SOURCE:
472 		g_value_set_object (value, priv->player_source);
473 		break;
474 	case PROP_PLAYLIST_PATH:
475 		g_value_set_string (value, priv->playlist_path);
476 		break;
477 	case PROP_DEVICE_ROOT:
478 		g_value_set_string (value, priv->device_root);
479 		break;
480 	default:
481 		G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
482 		break;
483 	}
484 }
485 
486 static void
487 impl_set_property (GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec)
488 {
489 	RBGenericPlayerPlaylistSourcePrivate *priv = GET_PRIVATE (object);
490 
491 	switch (prop_id) {
492 	case PROP_PLAYER_SOURCE:
493 		priv->player_source = RB_GENERIC_PLAYER_SOURCE (g_value_dup_object (value));
494 		break;
495 	case PROP_PLAYLIST_PATH:
496 		priv->playlist_path = g_value_dup_string (value);
497 		break;
498 	case PROP_DEVICE_ROOT:
499 		priv->device_root = g_value_dup_string (value);
500 		break;
501 	default:
502 		G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
503 		break;
504 	}
505 }
506 
507 static gboolean
508 impl_show_popup (RBDisplayPage *page)
509 {
510 	_rb_display_page_show_popup (page, "/GenericPlayerPlaylistSourcePopup");
511 	return TRUE;
512 }
513 
514 static void
515 rb_generic_player_playlist_source_class_init (RBGenericPlayerPlaylistSourceClass *klass)
516 {
517 	GObjectClass *object_class = G_OBJECT_CLASS (klass);
518 	RBDisplayPageClass *page_class = RB_DISPLAY_PAGE_CLASS (klass);
519 	RBSourceClass *source_class = RB_SOURCE_CLASS (klass);
520 	RBPlaylistSourceClass *playlist_class = RB_PLAYLIST_SOURCE_CLASS (klass);
521 
522 	object_class->dispose = impl_dispose;
523 	object_class->finalize = impl_finalize;
524 	object_class->get_property = impl_get_property;
525 	object_class->set_property = impl_set_property;
526 
527 	page_class->show_popup = impl_show_popup;
528 
529 	source_class->impl_can_move_to_trash = (RBSourceFeatureFunc) rb_false_function;
530 
531 	playlist_class->impl_save_contents_to_xml = impl_save_to_xml;
532 	playlist_class->impl_mark_dirty = impl_mark_dirty;
533 
534 	g_object_class_install_property (object_class,
535 					 PROP_PLAYER_SOURCE,
536 					 g_param_spec_object ("player-source",
537 						 	      "player-source",
538 							      "player source",
539 							      RB_TYPE_GENERIC_PLAYER_SOURCE,
540 							      G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
541 	g_object_class_install_property (object_class,
542 					 PROP_PLAYLIST_PATH,
543 					 g_param_spec_string ("playlist-path",
544 						 	      "playlist-path",
545 							      "path to playlist file",
546 							      NULL,
547 							      G_PARAM_READWRITE | G_PARAM_CONSTRUCT));
548 
549 	g_object_class_install_property (object_class,
550 					 PROP_DEVICE_ROOT,
551 					 g_param_spec_string ("device-root",
552 						 	      "device-root",
553 							      "path to root of the device",
554 							      NULL,
555 							      G_PARAM_READWRITE | G_PARAM_CONSTRUCT));
556 
557 	g_type_class_add_private (klass, sizeof (RBGenericPlayerPlaylistSourcePrivate));
558 }
559 
560 static void
561 rb_generic_player_playlist_source_class_finalize (RBGenericPlayerPlaylistSourceClass *klass)
562 {
563 }
564 
565 void
566 _rb_generic_player_playlist_source_register_type (GTypeModule *module)
567 {
568 	rb_generic_player_playlist_source_register_type (module);
569 }