hythmbox-2.98/sources/rb-playlist-source.c

Location Tool Test ID Function Issue
rb-playlist-source.c:830:2 clang-analyzer Access to field 'impl_save_contents_to_xml' results in a dereference of a null pointer (loaded from variable 'klass')
rb-playlist-source.c:830:2 clang-analyzer Access to field 'impl_save_contents_to_xml' results in a dereference of a null pointer (loaded from variable 'klass')
   1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
   2 /*
   3  *  Copyright (C) 2002 Jorn Baayen <jorn@nl.linux.org>
   4  *  Copyright (C) 2003 Colin Walters <walters@gnome.org>
   5  *
   6  *  This program is free software; you can redistribute it and/or modify
   7  *  it under the terms of the GNU General Public License as published by
   8  *  the Free Software Foundation; either version 2 of the License, or
   9  *  (at your option) any later version.
  10  *
  11  *  The Rhythmbox authors hereby grant permission for non-GPL compatible
  12  *  GStreamer plugins to be used and distributed together with GStreamer
  13  *  and Rhythmbox. This permission is above and beyond the permissions granted
  14  *  by the GPL license by which Rhythmbox is covered. If you modify this code
  15  *  you may extend this exception to your version of the code, but you are not
  16  *  obligated to do so. If you do not wish to do so, delete this exception
  17  *  statement from your version.
  18  *
  19  *  This program is distributed in the hope that it will be useful,
  20  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
  21  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  22  *  GNU General Public License for more details.
  23  *
  24  *  You should have received a copy of the GNU General Public License
  25  *  along with this program; if not, write to the Free Software
  26  *  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301  USA.
  27  *
  28  */
  29 
  30 #include "config.h"
  31 
  32 #include <unistd.h>
  33 #include <string.h>
  34 
  35 #include <libxml/tree.h>
  36 #include <glib/gi18n.h>
  37 #include <gtk/gtk.h>
  38 #include <totem-pl-parser.h>
  39 
  40 #include "rb-entry-view.h"
  41 #include "rb-search-entry.h"
  42 #include "rb-file-helpers.h"
  43 #include "rb-dialog.h"
  44 #include "rb-util.h"
  45 #include "rb-playlist-source.h"
  46 #include "rb-debug.h"
  47 #include "rb-song-info.h"
  48 
  49 #include "rb-playlist-xml.h"
  50 #include "rb-static-playlist-source.h"
  51 #include "rb-auto-playlist-source.h"
  52 
  53 #include "rb-playlist-manager.h"
  54 
  55 /**
  56  * SECTION:rb-playlist-source
  57  * @short_description: Base class for playlist sources
  58  *
  59  * This class provides some common infrastructure for playlist
  60  * sources.  A playlist, in this context, is a persistent user-defined
  61  * selection from a set of songs.  Playlists (static and auto) based 
  62  * on the main library are saved to the playlists.xml file stored
  63  * alongside the rhythmdb.xml file.  Playlists on portable music players
  64  * are saved on the device in the format the player itself supports.
  65  *
  66  * This class provides most of the source UI (excluding the search bar),
  67  * holds some of the framework for loading and saving the playlists.xml
  68  * file, and records which playlists need to be saved.
  69  */
  70 
  71 static void rb_playlist_source_class_init (RBPlaylistSourceClass *klass);
  72 static void rb_playlist_source_init (RBPlaylistSource *source);
  73 static void rb_playlist_source_constructed (GObject *object);
  74 static void rb_playlist_source_dispose (GObject *object);
  75 static void rb_playlist_source_finalize (GObject *object);
  76 static void rb_playlist_source_set_property (GObject *object,
  77 					     guint prop_id,
  78 					     const GValue *value,
  79 					     GParamSpec *pspec);
  80 static void rb_playlist_source_get_property (GObject *object,
  81 					     guint prop_id,
  82 					     GValue *value,
  83 					     GParamSpec *pspec);
  84 
  85 /* source methods */
  86 static RBEntryView *impl_get_entry_view (RBSource *source);
  87 static void impl_song_properties (RBSource *source);
  88 static gboolean impl_show_popup (RBDisplayPage *page);
  89 
  90 static void rb_playlist_source_songs_show_popup_cb (RBEntryView *view,
  91 						    gboolean over_entry,
  92 						    RBPlaylistSource *playlist_view);
  93 static void rb_playlist_source_drop_cb (GtkWidget *widget,
  94 				     GdkDragContext *context,
  95 				     gint x,
  96 				     gint y,
  97 				     GtkSelectionData *data,
  98 				     guint info,
  99 				     guint time,
 100 				     gpointer user_data);
 101 
 102 static void rb_playlist_source_row_deleted (GtkTreeModel *model,
 103 					    GtkTreePath *path,
 104 					    RBPlaylistSource *playlist);
 105 static void default_show_entry_view_popup (RBPlaylistSource *source,
 106 					   RBEntryView *view,
 107 					   gboolean over_entry);
 108 static void rb_playlist_source_entry_added_cb (RhythmDB *db, RhythmDBEntry *entry,
 109 					       RBPlaylistSource *source);
 110 static void rb_playlist_source_track_cell_data_func (GtkTreeViewColumn *column, GtkCellRenderer *renderer,
 111 						     GtkTreeModel *tree_model, GtkTreeIter *iter,
 112 						     RBPlaylistSource *source);
 113 static void default_mark_dirty (RBPlaylistSource *source);
 114 static void rb_playlist_source_songs_sort_order_changed_cb (GObject *object,
 115 							    GParamSpec *pspec,
 116 							    RBPlaylistSource *source);
 117 
 118 static void remove_from_playlist_cmd (GtkAction *action, RBSource *source);
 119 static char *impl_get_delete_action (RBSource *source);
 120 
 121 #define PLAYLIST_SOURCE_SONGS_POPUP_PATH "/PlaylistViewPopup"
 122 #define PLAYLIST_SOURCE_POPUP_PATH "/PlaylistSourcePopup"
 123 
 124 static GtkActionEntry rb_playlist_source_actions [] =
 125 {
 126 	{ "RemoveFromPlaylist", GTK_STOCK_REMOVE, N_("Remove From Playlist"), NULL,
 127 	  N_("Remove each selected song from the playlist"),
 128 	  G_CALLBACK (remove_from_playlist_cmd) },
 129 };
 130 
 131 
 132 struct RBPlaylistSourcePrivate
 133 {
 134 	RhythmDB *db;
 135 	GtkActionGroup *action_group;
 136 
 137 	GHashTable *entries;
 138 
 139 	RhythmDBQueryModel *model;
 140 
 141 	RBEntryView *songs;
 142 
 143 	gboolean dirty;
 144 	gboolean is_local;
 145 	gboolean dispose_has_run;
 146 
 147 	char *title;
 148 };
 149 
 150 #define RB_PLAYLIST_SOURCE_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), RB_TYPE_PLAYLIST_SOURCE, RBPlaylistSourcePrivate))
 151 
 152 enum
 153 {
 154 	PROP_0,
 155 	PROP_DB,
 156 	PROP_DIRTY,
 157 	PROP_LOCAL,
 158 };
 159 
 160 static const GtkTargetEntry target_uri [] = { { "text/uri-list", 0, 0 } };
 161 
 162 G_DEFINE_ABSTRACT_TYPE (RBPlaylistSource, rb_playlist_source, RB_TYPE_SOURCE);
 163 
 164 static void
 165 rb_playlist_source_class_init (RBPlaylistSourceClass *klass)
 166 {
 167 	GObjectClass *object_class = G_OBJECT_CLASS (klass);
 168 	RBDisplayPageClass *page_class = RB_DISPLAY_PAGE_CLASS (klass);
 169 	RBSourceClass *source_class = RB_SOURCE_CLASS (klass);
 170 
 171 	object_class->dispose = rb_playlist_source_dispose;
 172 	object_class->finalize = rb_playlist_source_finalize;
 173 	object_class->constructed = rb_playlist_source_constructed;
 174 	object_class->set_property = rb_playlist_source_set_property;
 175 	object_class->get_property = rb_playlist_source_get_property;
 176 
 177 	page_class->show_popup = impl_show_popup;
 178 
 179 	source_class->impl_get_entry_view = impl_get_entry_view;
 180 	source_class->impl_can_rename = (RBSourceFeatureFunc) rb_true_function;
 181 	source_class->impl_can_cut = (RBSourceFeatureFunc) rb_false_function;
 182 	source_class->impl_can_copy = (RBSourceFeatureFunc) rb_true_function;
 183 	source_class->impl_can_delete = (RBSourceFeatureFunc) rb_false_function;
 184 	source_class->impl_can_add_to_queue = (RBSourceFeatureFunc) rb_true_function;
 185 	source_class->impl_can_move_to_trash = (RBSourceFeatureFunc) rb_true_function;
 186 	source_class->impl_song_properties = impl_song_properties;
 187 	source_class->impl_get_delete_action = impl_get_delete_action;
 188 
 189 	klass->impl_show_entry_view_popup = default_show_entry_view_popup;
 190 	klass->impl_mark_dirty = default_mark_dirty;
 191 
 192 	/**
 193 	 * RBPlaylistSource:db:
 194 	 *
 195 	 * The #RhythmDB instance
 196 	 */
 197 	g_object_class_install_property (object_class,
 198 					 PROP_DB,
 199 					 g_param_spec_object ("db",
 200 							      "db",
 201 							      "rhythmdb instance",
 202 							      RHYTHMDB_TYPE,
 203 							      G_PARAM_READABLE));
 204 	/**
 205 	 * RBPlaylistSource:dirty:
 206 	 *
 207 	 * Whether the playlist has been changed since it was last saved
 208 	 * to disk.
 209 	 */
 210 	g_object_class_install_property (object_class,
 211 					 PROP_DIRTY,
 212 					 g_param_spec_boolean ("dirty",
 213 							       "dirty",
 214 							       "whether this playlist should be saved",
 215 							       FALSE,
 216 							       G_PARAM_READABLE));
 217 	/**
 218 	 * RBPlaylistSource:is-local:
 219 	 *
 220 	 * Whether the playlist is attached to the local library.
 221 	 * Remote DAAP playlists, for example, are not local.
 222 	 */
 223 	g_object_class_install_property (object_class,
 224 					 PROP_LOCAL,
 225 					 g_param_spec_boolean ("is-local",
 226 							       "is-local",
 227 							       "whether this playlist is attached to the local library",
 228 							       TRUE,
 229 							       G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
 230 
 231 	g_type_class_add_private (klass, sizeof (RBPlaylistSourcePrivate));
 232 }
 233 
 234 static void
 235 rb_playlist_source_init (RBPlaylistSource *source)
 236 {
 237 	source->priv = RB_PLAYLIST_SOURCE_GET_PRIVATE (source);
 238 }
 239 
 240 static void
 241 rb_playlist_source_set_db (RBPlaylistSource *source,
 242 			   RhythmDB         *db)
 243 {
 244 	if (source->priv->db != NULL) {
 245 		g_signal_handlers_disconnect_by_func (source->priv->db,
 246 						      rb_playlist_source_entry_added_cb,
 247 						      source);
 248 		g_object_unref (source->priv->db);
 249 
 250 	}
 251 
 252 	source->priv->db = db;
 253 
 254 	if (source->priv->db != NULL) {
 255 		g_object_ref (source->priv->db);
 256 		g_signal_connect_object (G_OBJECT (source->priv->db), "entry_added",
 257 					 G_CALLBACK (rb_playlist_source_entry_added_cb),
 258 					 source, 0);
 259 	}
 260 
 261 }
 262 
 263 static void
 264 rb_playlist_source_constructed (GObject *object)
 265 {
 266 	GObject *shell_player;
 267 	RBPlaylistSource *source;
 268 	RBShell *shell;
 269 	RhythmDB *db;
 270 	RhythmDBQueryModel *query_model;
 271 
 272 	RB_CHAIN_GOBJECT_METHOD (rb_playlist_source_parent_class, constructed, object);
 273 	source = RB_PLAYLIST_SOURCE (object);
 274 
 275 	g_object_get (source, "shell", &shell, NULL);
 276 	g_object_get (shell,
 277 		      "db", &db,
 278 		      "shell-player", &shell_player,
 279 		      NULL);
 280 	rb_playlist_source_set_db (source, db);
 281 	g_object_unref (db);
 282 
 283 	source->priv->action_group = _rb_display_page_register_action_group (RB_DISPLAY_PAGE (source),
 284 									     "PlaylistActions",
 285 									     NULL, 0,
 286 									     shell);
 287 	_rb_action_group_add_display_page_actions (source->priv->action_group,
 288 						   G_OBJECT (shell),
 289 						   rb_playlist_source_actions,
 290 						   G_N_ELEMENTS (rb_playlist_source_actions));
 291 
 292 	g_object_unref (shell);
 293 
 294 	source->priv->entries = g_hash_table_new_full (rb_refstring_hash, rb_refstring_equal,
 295 						       (GDestroyNotify)rb_refstring_unref, NULL);
 296 
 297 	source->priv->songs = rb_entry_view_new (source->priv->db,
 298 						 shell_player,
 299 						 TRUE, TRUE);
 300 	g_object_unref (shell_player);
 301 
 302 	g_signal_connect_object (source->priv->songs,
 303 				 "notify::sort-order",
 304 				 G_CALLBACK (rb_playlist_source_songs_sort_order_changed_cb),
 305 				 source, 0);
 306 
 307 	query_model = rhythmdb_query_model_new_empty (source->priv->db);
 308 	rb_playlist_source_set_query_model (source, query_model);
 309 	g_object_unref (query_model);
 310 
 311 	{
 312 		const char *title = "";
 313 		const char *strings[3] = {0};
 314 
 315 		GtkTreeViewColumn *column = gtk_tree_view_column_new ();
 316 		GtkCellRenderer *renderer = gtk_cell_renderer_text_new ();
 317 
 318 		g_object_set(renderer,
 319 			     "style", PANGO_STYLE_OBLIQUE,
 320 			     "weight", PANGO_WEIGHT_LIGHT,
 321 			     "xalign", 1.0,
 322 			     NULL);
 323 
 324 		gtk_tree_view_column_pack_start (column, renderer, TRUE);
 325 
 326 		gtk_tree_view_column_set_resizable (column, TRUE);
 327 		gtk_tree_view_column_set_sizing (column, GTK_TREE_VIEW_COLUMN_FIXED);
 328 
 329 		strings[0] = title;
 330 		strings[1] = "9999";
 331 		rb_entry_view_set_fixed_column_width (source->priv->songs, column, renderer,
 332 						      strings);
 333 		gtk_tree_view_column_set_cell_data_func (column, renderer,
 334 							 (GtkTreeCellDataFunc)
 335 							 rb_playlist_source_track_cell_data_func,
 336 							 source, NULL);
 337 		rb_entry_view_insert_column_custom (source->priv->songs, column, title,
 338 						    "PlaylistTrack", NULL, 0, NULL, 0);
 339 	}
 340 
 341 	rb_entry_view_append_column (source->priv->songs, RB_ENTRY_VIEW_COL_TRACK_NUMBER, FALSE);
 342 	rb_entry_view_append_column (source->priv->songs, RB_ENTRY_VIEW_COL_TITLE, TRUE);
 343 	rb_entry_view_append_column (source->priv->songs, RB_ENTRY_VIEW_COL_GENRE, FALSE);
 344 	rb_entry_view_append_column (source->priv->songs, RB_ENTRY_VIEW_COL_ARTIST, FALSE);
 345 	rb_entry_view_append_column (source->priv->songs, RB_ENTRY_VIEW_COL_ALBUM, FALSE);
 346 	rb_entry_view_append_column (source->priv->songs, RB_ENTRY_VIEW_COL_YEAR, FALSE);
 347 	rb_entry_view_append_column (source->priv->songs, RB_ENTRY_VIEW_COL_DURATION, FALSE);
 348  	rb_entry_view_append_column (source->priv->songs, RB_ENTRY_VIEW_COL_QUALITY, FALSE);
 349 	rb_entry_view_append_column (source->priv->songs, RB_ENTRY_VIEW_COL_RATING, FALSE);
 350 	rb_entry_view_append_column (source->priv->songs, RB_ENTRY_VIEW_COL_PLAY_COUNT, FALSE);
 351 	rb_entry_view_append_column (source->priv->songs, RB_ENTRY_VIEW_COL_COMMENT, FALSE);
 352 	rb_entry_view_append_column (source->priv->songs, RB_ENTRY_VIEW_COL_LOCATION, FALSE);
 353 	rb_entry_view_append_column (source->priv->songs, RB_ENTRY_VIEW_COL_LAST_PLAYED, FALSE);
 354 	rb_entry_view_append_column (source->priv->songs, RB_ENTRY_VIEW_COL_FIRST_SEEN, FALSE);
 355 	rb_entry_view_append_column (source->priv->songs, RB_ENTRY_VIEW_COL_BPM, FALSE);
 356 	rb_entry_view_set_columns_clickable (source->priv->songs, FALSE);
 357 
 358 	rb_playlist_source_setup_entry_view (source, source->priv->songs);
 359 
 360 	gtk_container_add (GTK_CONTAINER (source), GTK_WIDGET (source->priv->songs));
 361 
 362 	gtk_widget_show_all (GTK_WIDGET (source));
 363 }
 364 
 365 static void
 366 rb_playlist_source_dispose (GObject *object)
 367 {
 368 	RBPlaylistSource *source = RB_PLAYLIST_SOURCE (object);
 369 
 370 	if (source->priv->dispose_has_run) {
 371 		/* If dispose did already run, return. */
 372 		rb_debug ("Dispose has already run for playlist source %p", object);
 373 		return;
 374 	}
 375 	/* Make sure dispose does not run twice. */
 376 	source->priv->dispose_has_run = TRUE;
 377 
 378 	rb_debug ("Disposing playlist source %p", source);
 379 
 380 	if (source->priv->db != NULL) {
 381 		g_object_unref (source->priv->db);
 382 		source->priv->db = NULL;
 383 	}
 384 
 385 	if (source->priv->model != NULL) {
 386 		g_object_unref (source->priv->model);
 387 		source->priv->model = NULL;
 388 	}
 389 
 390 	G_OBJECT_CLASS (rb_playlist_source_parent_class)->dispose (object);
 391 }
 392 
 393 static void
 394 rb_playlist_source_finalize (GObject *object)
 395 {
 396 	RBPlaylistSource *source;
 397 
 398 	g_return_if_fail (object != NULL);
 399 	g_return_if_fail (RB_IS_PLAYLIST_SOURCE (object));
 400 
 401 	source = RB_PLAYLIST_SOURCE (object);
 402 	g_return_if_fail (source->priv != NULL);
 403 
 404 	rb_debug ("Finalizing playlist source %p", source);
 405 
 406 	g_hash_table_destroy (source->priv->entries);
 407 
 408 	g_free (source->priv->title);
 409 	source->priv = NULL;
 410 
 411 	G_OBJECT_CLASS (rb_playlist_source_parent_class)->finalize (object);
 412 }
 413 
 414 static void
 415 rb_playlist_source_set_property (GObject *object,
 416 				 guint prop_id,
 417 				 const GValue *value,
 418 				 GParamSpec *pspec)
 419 {
 420 	RBPlaylistSource *source = RB_PLAYLIST_SOURCE (object);
 421 
 422 	switch (prop_id) {
 423 	case PROP_LOCAL:
 424 		source->priv->is_local = g_value_get_boolean (value);
 425 		break;
 426 	default:
 427 		G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
 428 		break;
 429 	}
 430 }
 431 
 432 static void
 433 rb_playlist_source_get_property (GObject *object,
 434 				 guint prop_id,
 435 				 GValue *value,
 436 				 GParamSpec *pspec)
 437 {
 438 	RBPlaylistSource *source = RB_PLAYLIST_SOURCE (object);
 439 
 440 	switch (prop_id) {
 441 	case PROP_DB:
 442 		g_value_set_object (value, source->priv->db);
 443 		break;
 444 	case PROP_DIRTY:
 445 		g_value_set_boolean (value, source->priv->dirty);
 446 		break;
 447 	case PROP_LOCAL:
 448 		g_value_set_boolean (value, source->priv->is_local);
 449 		break;
 450 	default:
 451 		G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
 452 		break;
 453 	}
 454 }
 455 
 456 static void
 457 default_show_entry_view_popup (RBPlaylistSource *source,
 458 			       RBEntryView *view,
 459 			       gboolean over_entry)
 460 {
 461 	if (over_entry) {
 462 		_rb_display_page_show_popup (RB_DISPLAY_PAGE (source), PLAYLIST_SOURCE_SONGS_POPUP_PATH);
 463 	}
 464 }
 465 
 466 static void
 467 rb_playlist_source_songs_show_popup_cb (RBEntryView *view,
 468 					gboolean over_entry,
 469 					RBPlaylistSource *source)
 470 {
 471 	RBPlaylistSourceClass *klass = RB_PLAYLIST_SOURCE_GET_CLASS (source);
 472 	if (klass->impl_show_entry_view_popup)
 473 		klass->impl_show_entry_view_popup (source, view, over_entry);
 474 }
 475 
 476 static RBEntryView *
 477 impl_get_entry_view (RBSource *asource)
 478 {
 479 	RBPlaylistSource *source = RB_PLAYLIST_SOURCE (asource);
 480 
 481 	return source->priv->songs;
 482 }
 483 
 484 static void
 485 impl_song_properties (RBSource *asource)
 486 {
 487 	RBPlaylistSource *source = RB_PLAYLIST_SOURCE (asource);
 488 	GtkWidget *song_info = NULL;
 489 
 490 	g_return_if_fail (source->priv->songs != NULL);
 491 
 492 	song_info = rb_song_info_new (asource, NULL);
 493 	if (song_info)
 494 		gtk_widget_show_all (song_info);
 495 	else
 496 		rb_debug ("failed to create dialog, or no selection!");
 497 }
 498 
 499 static gboolean
 500 impl_show_popup (RBDisplayPage *page)
 501 {
 502 	_rb_display_page_show_popup (page, PLAYLIST_SOURCE_POPUP_PATH);
 503 	return TRUE;
 504 }
 505 
 506 static void
 507 rb_playlist_source_drop_cb (GtkWidget *widget,
 508 			    GdkDragContext *context,
 509 			    gint x,
 510 			    gint y,
 511 			    GtkSelectionData *data,
 512 			    guint info,
 513 			    guint time,
 514 			    gpointer user_data)
 515 {
 516 	RBPlaylistSource *source = RB_PLAYLIST_SOURCE (user_data);
 517 	GtkTargetList *tlist;
 518 	GdkAtom target;
 519 
 520 	tlist = gtk_target_list_new (target_uri, G_N_ELEMENTS (target_uri));
 521 	target = gtk_drag_dest_find_target (widget, context, tlist);
 522 	gtk_target_list_unref (tlist);
 523 
 524 	if (target == GDK_NONE)
 525 		return;
 526 
 527 	rb_display_page_receive_drag (RB_DISPLAY_PAGE (source), data);
 528 
 529 	gtk_drag_finish (context, TRUE, FALSE, time);
 530 }
 531 
 532 #if TOTEM_PL_PARSER_CHECK_VERSION(2,29,1)
 533 
 534 static void
 535 set_field_from_property (TotemPlPlaylist *playlist,
 536 			 TotemPlPlaylistIter *iter,
 537 			 RhythmDBEntry *entry,
 538 			 RhythmDBPropType property,
 539 			 const char *field)
 540 {
 541 	const char *value;
 542 
 543 	value = rhythmdb_entry_get_string (entry, property);
 544 	if (value != NULL) {
 545 		totem_pl_playlist_set (playlist, iter, field, value, NULL);
 546 	}
 547 }
 548 
 549 static gboolean
 550 playlist_iter_foreach (GtkTreeModel *model,
 551 		       GtkTreePath *path,
 552 		       GtkTreeIter *iter,
 553 		       TotemPlPlaylist *playlist)
 554 {
 555 	TotemPlPlaylistIter pl_iter;
 556 	RhythmDBEntry *entry;
 557 
 558 	gtk_tree_model_get (model, iter, 0, &entry, -1);
 559 	if (entry == NULL) {
 560 		return FALSE;
 561 	}
 562 
 563 	totem_pl_playlist_append (playlist, &pl_iter);
 564 	set_field_from_property (playlist, &pl_iter, entry, RHYTHMDB_PROP_LOCATION, TOTEM_PL_PARSER_FIELD_URI);
 565 	set_field_from_property (playlist, &pl_iter, entry, RHYTHMDB_PROP_ARTIST, TOTEM_PL_PARSER_FIELD_AUTHOR);
 566 	set_field_from_property (playlist, &pl_iter, entry, RHYTHMDB_PROP_GENRE, TOTEM_PL_PARSER_FIELD_GENRE);
 567 	set_field_from_property (playlist, &pl_iter, entry, RHYTHMDB_PROP_ALBUM, TOTEM_PL_PARSER_FIELD_ALBUM);
 568 	set_field_from_property (playlist, &pl_iter, entry, RHYTHMDB_PROP_TITLE, TOTEM_PL_PARSER_FIELD_TITLE);
 569 
 570 	/* could possibly set duration, file size.. ? */
 571 
 572 	return FALSE;
 573 }
 574 
 575 
 576 #else
 577 
 578 static void
 579 playlist_iter_func (GtkTreeModel *model,
 580 		    GtkTreeIter *iter,
 581 		    char **uri,
 582 		    char **title,
 583 		    gboolean *custom_title,
 584 		    gpointer user_data)
 585 {
 586 	RhythmDBEntry *entry;
 587 
 588 	gtk_tree_model_get (model, iter, 0, &entry, -1);
 589 
 590 	if (uri != NULL) {
 591 		*uri = rhythmdb_entry_dup_string (entry, RHYTHMDB_PROP_LOCATION);
 592 	}
 593 	if (title != NULL) {
 594 		*title = rhythmdb_entry_dup_string (entry, RHYTHMDB_PROP_TITLE);
 595 	}
 596 	if (custom_title != NULL) {
 597 		*custom_title = TRUE;
 598 	}
 599 
 600 	if (entry != NULL) {
 601 		rhythmdb_entry_unref (entry);
 602 	}
 603 }
 604 
 605 #endif
 606 
 607 /**
 608  * rb_playlist_source_save_playlist:
 609  * @source: a #RBPlaylistSource
 610  * @uri: destination URI
 611  * @export_type: format to save in
 612  *
 613  * Saves the playlist to an external file in a standard
 614  * format (M3U, PLS, or XSPF).
 615  */
 616 void
 617 rb_playlist_source_save_playlist (RBPlaylistSource *source,
 618 				  const char *uri,
 619 				  RBPlaylistExportType export_type)
 620 {
 621 	TotemPlParser *pl;
 622 	GError *error = NULL;
 623 	char *name;
 624 	gint totem_format;
 625 #if TOTEM_PL_PARSER_CHECK_VERSION(2,29,1)
 626 	TotemPlPlaylist *playlist;
 627 	GFile *file;
 628 #endif
 629 
 630 	g_return_if_fail (RB_IS_PLAYLIST_SOURCE (source));
 631 
 632 	rb_debug ("saving playlist");
 633 	pl = totem_pl_parser_new ();
 634 
 635 	g_object_get (source, "name", &name, NULL);
 636 
 637 	switch (export_type) {
 638 	case RB_PLAYLIST_EXPORT_TYPE_XSPF:
 639 		totem_format = TOTEM_PL_PARSER_XSPF;
 640 		break;
 641 	case RB_PLAYLIST_EXPORT_TYPE_M3U:
 642 		totem_format = TOTEM_PL_PARSER_M3U;
 643 		break;
 644 	case RB_PLAYLIST_EXPORT_TYPE_PLS:
 645 	default:
 646 		totem_format = TOTEM_PL_PARSER_PLS;
 647 		break;
 648 	}
 649 
 650 #if TOTEM_PL_PARSER_CHECK_VERSION(2,29,1)
 651 	file = g_file_new_for_uri (uri);
 652 	playlist = totem_pl_playlist_new ();
 653 
 654 	gtk_tree_model_foreach (GTK_TREE_MODEL (source->priv->model),
 655 				(GtkTreeModelForeachFunc)playlist_iter_foreach,
 656 				playlist);
 657 	totem_pl_parser_save (pl, playlist, file, name, totem_format, &error);
 658 	g_object_unref (playlist);
 659 	g_object_unref (file);
 660 #else
 661 	totem_pl_parser_write_with_title (pl, GTK_TREE_MODEL (source->priv->model),
 662 					  playlist_iter_func, uri, name,
 663 					  totem_format,
 664 					  NULL, &error);
 665 #endif
 666 	g_object_unref (pl);
 667 	g_free (name);
 668 	if (error != NULL) {
 669 		rb_error_dialog (NULL, _("Couldn't save playlist"),
 670 				 "%s", error->message);
 671 		g_error_free (error);
 672 	}
 673 }
 674 
 675 /* Adapted from yelp-toc-pager.c */
 676 static xmlChar *
 677 xml_get_and_trim_names (xmlNodePtr node)
 678 {
 679 	xmlNodePtr cur;
 680 	xmlChar *keep_lang = NULL;
 681 	xmlChar *value;
 682 	int j, keep_pri = INT_MAX;
 683 
 684 	const gchar * const * langs = g_get_language_names ();
 685 
 686 	value = NULL;
 687 
 688 	for (cur = node->children; cur; cur = cur->next) {
 689 		if (! xmlStrcmp (cur->name, RB_PLAYLIST_NAME)) {
 690 			xmlChar *cur_lang = NULL;
 691 			int cur_pri = INT_MAX;
 692 
 693 			cur_lang = xmlNodeGetLang (cur);
 694 
 695 			if (cur_lang) {
 696 				for (j = 0; langs[j]; j++) {
 697 					if (g_str_equal (cur_lang, langs[j])) {
 698 						cur_pri = j;
 699 						break;
 700 					}
 701 				}
 702 			} else {
 703 				cur_pri = INT_MAX - 1;
 704 			}
 705 
 706 			if (cur_pri <= keep_pri) {
 707 				if (keep_lang)
 708 					xmlFree (keep_lang);
 709 				if (value)
 710 					xmlFree (value);
 711 
 712 				value = xmlNodeGetContent (cur);
 713 
 714 				keep_lang = cur_lang;
 715 				keep_pri = cur_pri;
 716 			} else {
 717 				if (cur_lang)
 718 					xmlFree (cur_lang);
 719 			}
 720 		}
 721 	}
 722 
 723 	/* Delete all RB_PLAYLIST_NAME nodes */
 724 	cur = node->children;
 725 	while (cur) {
 726 		xmlNodePtr this = cur;
 727 		cur = cur->next;
 728 		if (! xmlStrcmp (this->name, RB_PLAYLIST_NAME)) {
 729 			xmlUnlinkNode (this);
 730 			xmlFreeNode (this);
 731 		}
 732 	}
 733 
 734 	return value;
 735 }
 736 
 737 static xmlChar *
 738 get_playlist_name_from_xml (xmlNodePtr node)
 739 {
 740 	xmlChar *name;
 741 
 742 	/* try to get and trim elements */
 743 	name = xml_get_and_trim_names (node);
 744 
 745 	if (name != NULL) {
 746 		return name;
 747 	}
 748 
 749 	/* try the attribute */
 750 	name = xmlGetProp (node, RB_PLAYLIST_NAME);
 751 
 752 	return name;
 753 }
 754 
 755 /**
 756  * rb_playlist_source_new_from_xml:
 757  * @shell: the #RBShell instance
 758  * @node: libxml node containing the playlist
 759  *
 760  * Constructs a playlist source instance from the XML serialized
 761  * format.  This function knows about all the playlist types that
 762  * can be saved to disk, and it hands off the XML node to the
 763  * appropriate constructor based on the 'type' attribute of
 764  * the root node of the playlist.
 765  *
 766  * Return value: the playlist
 767  */
 768 RBSource *
 769 rb_playlist_source_new_from_xml	(RBShell *shell,
 770 				 xmlNodePtr node)
 771 {
 772 	RBSource *source = NULL;
 773 	xmlChar *tmp;
 774 	xmlChar *name;
 775 
 776 	g_return_val_if_fail (RB_IS_SHELL (shell), NULL);
 777 
 778 	/* Try to get name from XML and remove translated names */
 779 	name = get_playlist_name_from_xml (node);
 780 
 781 	tmp = xmlGetProp (node, RB_PLAYLIST_TYPE);
 782 
 783 	if (!xmlStrcmp (tmp, RB_PLAYLIST_AUTOMATIC))
 784 		source = rb_auto_playlist_source_new_from_xml (shell, node);
 785 	else if (!xmlStrcmp (tmp, RB_PLAYLIST_STATIC))
 786 		source = rb_static_playlist_source_new_from_xml (shell, node);
 787 	else if (!xmlStrcmp (tmp, RB_PLAYLIST_QUEUE)) {
 788 		RBStaticPlaylistSource *queue;
 789 
 790 		g_object_get (shell, "queue-source", &queue, NULL);
 791 		rb_static_playlist_source_load_from_xml (queue, node);
 792 		g_object_unref (queue);
 793 	} else {
 794 		g_warning ("attempting to load playlist '%s' of unknown type '%s'", name, tmp);
 795 	}
 796 
 797 	if (source != NULL) {
 798 		g_object_set (G_OBJECT (source), "name", name, NULL);
 799 	}
 800 
 801 	xmlFree (name);
 802 	xmlFree (tmp);
 803 
 804 	return source;
 805 }
 806 
 807 /**
 808  * rb_playlist_source_save_to_xml:
 809  * @source: the playlist source to save
 810  * @parent_node: libxml node below which to save the playlist
 811  *
 812  * Converts the playlist to XML format, below the specified
 813  * parent node.
 814  */
 815 void
 816 rb_playlist_source_save_to_xml (RBPlaylistSource *source,
 817 				xmlNodePtr parent_node)
 818 {
 819 	xmlNodePtr node;
 820 	xmlChar *name;
 821 	RBPlaylistSourceClass *klass = RB_PLAYLIST_SOURCE_GET_CLASS (source);
 822 
 823 	g_return_if_fail (RB_IS_PLAYLIST_SOURCE (source));
 824 
 825 	node = xmlNewChild (parent_node, NULL, RB_PLAYLIST_PLAYLIST, NULL);
 826 	g_object_get (source, "name", &name, NULL);
 827 	xmlSetProp (node, RB_PLAYLIST_NAME, name);
 828 	g_free (name);
 829 
 830 	klass->impl_save_contents_to_xml (source, node);
Access to field 'impl_save_contents_to_xml' results in a dereference of a null pointer (loaded from variable 'klass')
(emitted by clang-analyzer)

TODO: a detailed trace is available in the data model (not yet rendered in this report)

Access to field 'impl_save_contents_to_xml' results in a dereference of a null pointer (loaded from variable 'klass')
(emitted by clang-analyzer)

TODO: a detailed trace is available in the data model (not yet rendered in this report)

831 832 source->priv->dirty = FALSE; 833 } 834 835 static void 836 rb_playlist_source_row_deleted (GtkTreeModel *model, 837 GtkTreePath *path, 838 RBPlaylistSource *source) 839 { 840 RhythmDBEntry *entry; 841 RBRefString *location; 842 843 entry = rhythmdb_query_model_tree_path_to_entry (RHYTHMDB_QUERY_MODEL (model), 844 path); 845 846 location = rhythmdb_entry_get_refstring (entry, RHYTHMDB_PROP_LOCATION); 847 if (g_hash_table_remove (source->priv->entries, location)) 848 source->priv->dirty = TRUE; 849 850 rb_refstring_unref (location); 851 rhythmdb_entry_unref (entry); 852 } 853 854 static void 855 rb_playlist_source_entry_added_cb (RhythmDB *db, 856 RhythmDBEntry *entry, 857 RBPlaylistSource *source) 858 { 859 RBRefString *location; 860 861 location = rhythmdb_entry_get_refstring (entry, RHYTHMDB_PROP_LOCATION); 862 863 if (g_hash_table_lookup (source->priv->entries, location)) { 864 if (_rb_source_check_entry_type (RB_SOURCE (source), entry)) { 865 rhythmdb_query_model_add_entry (source->priv->model, entry, -1); 866 source->priv->dirty = TRUE; 867 } else { 868 g_hash_table_remove (source->priv->entries, location); 869 } 870 } 871 872 rb_refstring_unref (location); 873 } 874 875 static void 876 rb_playlist_source_track_cell_data_func (GtkTreeViewColumn *column, 877 GtkCellRenderer *renderer, 878 GtkTreeModel *tree_model, 879 GtkTreeIter *iter, 880 RBPlaylistSource *source) 881 { 882 char *str; 883 int val; 884 885 gtk_tree_model_get (tree_model, iter, 1, &val, -1); 886 887 if (val >= 0) 888 str = g_strdup_printf ("%d", val); 889 else 890 str = g_strdup (""); 891 892 g_object_set (G_OBJECT (renderer), "text", str, NULL); 893 g_free (str); 894 } 895 896 /** 897 * rb_playlist_source_setup_entry_view: 898 * @source: the #RBPlaylistSource 899 * @entry_view: the new #RBEntryView to set up 900 * 901 * Connects signal handlers and sets up drag and drop support for 902 * an entry view to be used by a playlist source. This only needs 903 * to be called if the playlist subclass is creating a new entry view. 904 */ 905 void 906 rb_playlist_source_setup_entry_view (RBPlaylistSource *source, 907 RBEntryView *entry_view) 908 { 909 g_return_if_fail (RB_IS_PLAYLIST_SOURCE (source)); 910 911 g_signal_connect_object (entry_view, "show_popup", 912 G_CALLBACK (rb_playlist_source_songs_show_popup_cb), source, 0); 913 g_signal_connect_object (entry_view, "drag_data_received", 914 G_CALLBACK (rb_playlist_source_drop_cb), source, 0); 915 gtk_drag_dest_set (GTK_WIDGET (entry_view), 916 GTK_DEST_DEFAULT_ALL, 917 target_uri, 918 G_N_ELEMENTS (target_uri), 919 GDK_ACTION_COPY); 920 } 921 922 /** 923 * rb_playlist_source_set_query_model: 924 * @source: the #RBPlaylistSource 925 * @model: the new #RhythmDBQueryModel 926 * 927 * Sets a new query model for the playlist. This updates the 928 * entry view to use the new query model and also updates the 929 * source query-model property. 930 * 931 * This needs to be called when the playlist subclass 932 * creates a new query model. 933 */ 934 void 935 rb_playlist_source_set_query_model (RBPlaylistSource *source, 936 RhythmDBQueryModel *model) 937 { 938 g_return_if_fail (RB_IS_PLAYLIST_SOURCE (source)); 939 940 if (source->priv->model != NULL) { 941 /* if the query model is replaced, the set of entries in 942 * the playlist will change, so we should mark the playlist dirty. 943 */ 944 source->priv->dirty = TRUE; 945 g_signal_handlers_disconnect_by_func (source->priv->model, 946 G_CALLBACK (rb_playlist_source_row_deleted), 947 source); 948 g_object_unref (source->priv->model); 949 } 950 951 source->priv->model = model; 952 953 if (source->priv->model != NULL) { 954 g_object_ref (source->priv->model); 955 g_signal_connect_object (source->priv->model, "row_deleted", 956 G_CALLBACK (rb_playlist_source_row_deleted), source, 0); 957 } 958 959 rb_entry_view_set_model (source->priv->songs, RHYTHMDB_QUERY_MODEL (source->priv->model)); 960 961 g_object_set (source, "query-model", source->priv->model, NULL); 962 } 963 964 /** 965 * rb_playlist_source_get_db: 966 * @source: a #RBPlaylistSource 967 * 968 * Returns the #RhythmDB instance. The caller must not 969 * unref the object once finished with it. 970 * 971 * Return value: (transfer none): the #RhythmDB instance 972 */ 973 RhythmDB * 974 rb_playlist_source_get_db (RBPlaylistSource *source) 975 { 976 g_return_val_if_fail (RB_IS_PLAYLIST_SOURCE (source), NULL); 977 978 return source->priv->db; 979 } 980 981 /** 982 * rb_playlist_source_get_query_model: 983 * @source: a #RBPlaylistSource 984 * 985 * Returns the current #RhythmDBQueryModel for the playlist. 986 * The caller must not unref the object once finished with it. 987 * 988 * Return value: (transfer none): the current #RhythmDBQueryModel 989 */ 990 RhythmDBQueryModel * 991 rb_playlist_source_get_query_model (RBPlaylistSource *source) 992 { 993 g_return_val_if_fail (RB_IS_PLAYLIST_SOURCE (source), NULL); 994 995 return source->priv->model; 996 } 997 998 static void 999 default_mark_dirty (RBPlaylistSource *source) 1000 { 1001 source->priv->dirty = TRUE; 1002 } 1003 1004 /** 1005 * rb_playlist_source_mark_dirty: 1006 * @source: a #RBPlaylistSource 1007 * 1008 * Marks the playlist dirty. This generally means that the playlist 1009 * will be saved to disk fairly soon, but the exact meaning can vary 1010 * between playlist types. 1011 */ 1012 void 1013 rb_playlist_source_mark_dirty (RBPlaylistSource *source) 1014 { 1015 RBPlaylistSourceClass *klass; 1016 g_return_if_fail (RB_IS_PLAYLIST_SOURCE (source)); 1017 1018 klass = RB_PLAYLIST_SOURCE_GET_CLASS (source); 1019 klass->impl_mark_dirty (source); 1020 g_object_notify (G_OBJECT (source), "dirty"); 1021 } 1022 1023 /** 1024 * rb_playlist_source_location_in_map: 1025 * @source: a #RBPlaylistSource 1026 * @location: a URI to check 1027 * 1028 * Returns TRUE if the specified URI is in the playlist entry map 1029 * 1030 * Return value: %TRUE if the URI is present 1031 */ 1032 gboolean 1033 rb_playlist_source_location_in_map (RBPlaylistSource *source, 1034 const char *location) 1035 { 1036 RBRefString *refstr; 1037 gboolean found; 1038 1039 g_return_val_if_fail (RB_IS_PLAYLIST_SOURCE (source), FALSE); 1040 1041 refstr = rb_refstring_find (location); 1042 if (refstr == NULL) { 1043 return FALSE; 1044 } 1045 1046 found = (g_hash_table_lookup (source->priv->entries, refstr) != NULL); 1047 rb_refstring_unref (refstr); 1048 1049 return found; 1050 } 1051 1052 /** 1053 * rb_playlist_source_add_to_map: 1054 * @source: a #RBPlaylistSource 1055 * @location: a URI to add 1056 * 1057 * Adds a URI to the playlist's entry map. This is useful when the 1058 * URI is being added to the database, but no entry exists for it yet. 1059 * When the entry is created, it will be added to the query model. 1060 * 1061 * Return value: TRUE if the URI was added to the entry map, 1062 * FALSE if it was already there. 1063 */ 1064 gboolean 1065 rb_playlist_source_add_to_map (RBPlaylistSource *source, 1066 const char *location) 1067 { 1068 RBRefString *refstr; 1069 1070 g_return_val_if_fail (RB_IS_PLAYLIST_SOURCE (source), FALSE); 1071 1072 refstr = rb_refstring_new (location); 1073 if (g_hash_table_lookup (source->priv->entries, refstr)) { 1074 rb_refstring_unref (refstr); 1075 return FALSE; 1076 } 1077 1078 g_hash_table_insert (source->priv->entries, 1079 refstr, GINT_TO_POINTER (1)); 1080 1081 return TRUE; 1082 } 1083 1084 static void 1085 rb_playlist_source_songs_sort_order_changed_cb (GObject *object, 1086 GParamSpec *pspec, 1087 RBPlaylistSource *source) 1088 { 1089 rb_debug ("sort order changed"); 1090 rb_entry_view_resort_model (RB_ENTRY_VIEW (object)); 1091 } 1092 1093 static void 1094 remove_from_playlist_cmd (GtkAction *action, RBSource *source) 1095 { 1096 rb_source_delete (source); 1097 } 1098 1099 static char * 1100 impl_get_delete_action (RBSource *source) 1101 { 1102 return g_strdup ("RemoveFromPlaylist"); 1103 }