hythmbox-2.98/plugins/dbus-media-server/rb-dbus-media-server-plugin.c

No issues found

Incomplete coverage

Tool Failure ID Location Function Message Data
clang-analyzer no-output-found rb-dbus-media-server-plugin.c Message(text='Unable to locate XML output from invoke-clang-analyzer') None
Failure running clang-analyzer ('no-output-found')
Message
Unable to locate XML output from invoke-clang-analyzer
   1 /*
   2  * rb-dbus-media-server-plugin.c
   3  *
   4  *  Copyright (C) 2010  Jonathan Matthew  <jonathan@d14n.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, or (at your option)
   9  * 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 #include <config.h>
  30 
  31 #include <string.h>
  32 #include <glib/gi18n-lib.h>
  33 #include <gmodule.h>
  34 #include <gtk/gtk.h>
  35 #include <glib.h>
  36 #include <glib-object.h>
  37 #include <gio/gio.h>
  38 
  39 #include <lib/rb-util.h>
  40 #include <lib/rb-debug.h>
  41 #include <lib/rb-gst-media-types.h>
  42 #include <plugins/rb-plugin-macros.h>
  43 #include <shell/rb-shell.h>
  44 #include <shell/rb-shell-player.h>
  45 #include <sources/rb-display-page-model.h>
  46 #include <sources/rb-playlist-source.h>
  47 #include <sources/rb-device-source.h>
  48 #include <rhythmdb/rhythmdb-property-model.h>
  49 
  50 #define RB_TYPE_DBUS_MEDIA_SERVER_PLUGIN	(rb_dbus_media_server_plugin_get_type ())
  51 #define RB_DBUS_MEDIA_SERVER_PLUGIN(o)		(G_TYPE_CHECK_INSTANCE_CAST ((o), RB_TYPE_DBUS_MEDIA_SERVER_PLUGIN, RBMediaServer2Plugin))
  52 #define RB_DBUS_MEDIA_SERVER_PLUGIN_CLASS(k)	(G_TYPE_CHECK_CLASS_CAST((k), RB_TYPE_DBUS_MEDIA_SERVER_PLUGIN, RBMediaServer2PluginClass))
  53 #define RB_IS_DBUS_MEDIA_SERVER_PLUGIN(o)	(G_TYPE_CHECK_INSTANCE_TYPE ((o), RB_TYPE_DBUS_MEDIA_SERVER_PLUGIN))
  54 #define RB_IS_DBUS_MEDIA_SERVER_PLUGIN_CLASS(k)	(G_TYPE_CHECK_CLASS_TYPE ((k), RB_TYPE_DBUS_MEDIA_SERVER_PLUGIN))
  55 #define RB_DBUS_MEDIA_SERVER_PLUGIN_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), RB_TYPE_DBUS_MEDIA_SERVER_PLUGIN, RBMediaServer2PluginClass))
  56 
  57 #include "dbus-media-server-spec.h"
  58 
  59 #define RB_MEDIASERVER2_BUS_NAME	MEDIA_SERVER2_BUS_NAME_PREFIX ".Rhythmbox"
  60 
  61 #define RB_MEDIASERVER2_PREFIX		"/org/gnome/UPnP/MediaServer2/"
  62 #define RB_MEDIASERVER2_ROOT		RB_MEDIASERVER2_PREFIX "Rhythmbox"
  63 #define RB_MEDIASERVER2_LIBRARY		RB_MEDIASERVER2_PREFIX "Library"
  64 #define RB_MEDIASERVER2_PLAYLISTS	RB_MEDIASERVER2_PREFIX "Playlists"
  65 #define RB_MEDIASERVER2_DEVICES		RB_MEDIASERVER2_PREFIX "Devices"
  66 #define RB_MEDIASERVER2_ENTRY_SUBTREE	RB_MEDIASERVER2_PREFIX "Entry"
  67 #define RB_MEDIASERVER2_ENTRY_PREFIX	RB_MEDIASERVER2_ENTRY_SUBTREE "/"
  68 
  69 typedef struct
  70 {
  71 	PeasExtensionBase parent;
  72 
  73 	GDBusNodeInfo *node_info;
  74 	guint name_own_id;
  75 
  76 	GDBusConnection *connection;
  77 
  78 	/* object/subtree registration ids */
  79 	guint root_reg_id[2];
  80 	gboolean root_updated;
  81 	guint entry_reg_id;
  82 
  83 	guint emit_updated_id;
  84 
  85 	/* source and category registrations */
  86 	GList *sources;
  87 	GList *categories;
  88 
  89 	GSettings *settings;
  90 	RhythmDB *db;
  91 	RBDisplayPageModel *display_page_model;
  92 } RBMediaServer2Plugin;
  93 
  94 typedef struct
  95 {
  96 	PeasExtensionBaseClass parent_class;
  97 } RBMediaServer2PluginClass;
  98 
  99 typedef struct
 100 {
 101 	char *name;
 102 	guint dbus_reg_id[2];
 103 	gboolean updated;
 104 	char *dbus_path;
 105 	char *parent_dbus_path;
 106 
 107 	gboolean (*match_source) (RBSource *source);
 108 
 109 	RBMediaServer2Plugin *plugin;
 110 } CategoryRegistrationData;
 111 
 112 typedef struct
 113 {
 114 	RBSource *source;
 115 	RhythmDBQueryModel *base_query_model;
 116 	guint dbus_reg_id[2];
 117 	gboolean updated;
 118 	char *dbus_path;
 119 	char *parent_dbus_path;
 120 
 121 	gboolean flat;
 122 	guint all_tracks_reg_id[2];
 123 	GList *properties;
 124 
 125 	RBMediaServer2Plugin *plugin;
 126 } SourceRegistrationData;
 127 
 128 typedef struct
 129 {
 130 	SourceRegistrationData *source_data;
 131 	char *dbus_path;
 132 	char *display_name;
 133 	guint dbus_object_id[2];
 134 	guint dbus_subtree_id;
 135 	RhythmDBPropType property;
 136 	RhythmDBPropertyModel *model;
 137 	gboolean updated;
 138 	GList *updated_values;
 139 } SourcePropertyRegistrationData;
 140 
 141 RB_DEFINE_PLUGIN(RB_TYPE_DBUS_MEDIA_SERVER_PLUGIN, RBMediaServer2Plugin, rb_dbus_media_server_plugin,)
 142 
 143 G_MODULE_EXPORT void peas_register_types (PeasObjectModule *module);
 144 
 145 static void unregister_source_container (RBMediaServer2Plugin *plugin, SourceRegistrationData *source_data, gboolean deactivating);
 146 static void emit_source_tracks_property_updates (RBMediaServer2Plugin *plugin, SourceRegistrationData *source_data);
 147 static void emit_property_value_property_updates (RBMediaServer2Plugin *plugin, SourcePropertyRegistrationData *source_data, RBRefString *value);
 148 static void emit_category_container_property_updates (RBMediaServer2Plugin *plugin, CategoryRegistrationData *category_data);
 149 static void emit_root_property_updates (RBMediaServer2Plugin *plugin);
 150 
 151 static void
 152 rb_dbus_media_server_plugin_init (RBMediaServer2Plugin *plugin)
 153 {
 154 }
 155 
 156 static void
 157 register_object (RBMediaServer2Plugin *plugin,
 158 		 const GDBusInterfaceVTable *vtable,
 159 		 GDBusInterfaceInfo *iface_info,
 160 		 const char *object_path,
 161 		 gpointer method_data,
 162 		 guint *ids)
 163 {
 164 	GError *error = NULL;
 165 	GDBusInterfaceInfo *object_iface;
 166 	object_iface = g_dbus_node_info_lookup_interface (plugin->node_info, MEDIA_SERVER2_OBJECT_IFACE_NAME);
 167 
 168 	ids[0] = g_dbus_connection_register_object (plugin->connection,
 169 						    object_path,
 170 						    object_iface,
 171 						    vtable,
 172 						    method_data,
 173 						    NULL,
 174 						    &error);
 175 	if (error != NULL) {
 176 		g_warning ("Unable to register MediaServer2 object %s: %s",
 177 			   object_path,
 178 			   error->message);
 179 		g_clear_error (&error);
 180 	}
 181 
 182 	ids[1] = g_dbus_connection_register_object (plugin->connection,
 183 						    object_path,
 184 						    iface_info,
 185 						    vtable,
 186 						    method_data,
 187 						    NULL,
 188 						    &error);
 189 	if (error != NULL) {
 190 		g_warning ("Unable to register MediaServer2 object %s: %s",
 191 			   object_path,
 192 			   error->message);
 193 		g_clear_error (&error);
 194 	}
 195 }
 196 
 197 static void
 198 unregister_object (RBMediaServer2Plugin *plugin, guint *ids)
 199 {
 200 	if (ids[0] != 0) {
 201 		g_dbus_connection_unregister_object (plugin->connection, ids[0]);
 202 		ids[0] = 0;
 203 	}
 204 	if (ids[1] != 0) {
 205 		g_dbus_connection_unregister_object (plugin->connection, ids[1]);
 206 		ids[1] = 0;
 207 	}
 208 }
 209 
 210 /* entry subtree */
 211 
 212 char *all_entry_properties[] = {
 213 	"Parent",
 214 	"Type",
 215 	"Path",
 216 	"DisplayName",
 217 	"URLs",
 218 	"MIMEType",
 219 	"Size",
 220 	"Artist",
 221 	"Album",
 222 	"Date",
 223 	"Genre",
 224 	"DLNAProfile",
 225 	"Duration",
 226 	"Bitrate",
 227 	"AlbumArt",
 228 	"TrackNumber"
 229 };
 230 
 231 /* not used yet, since album art isn't exposed
 232 static gboolean
 233 entry_extra_metadata_maps (const char *extra_metadata)
 234 {
 235 	if (g_strcmp0 (extra_metadata, RHYTHMDB_PROP_COVER_ART_URI) == 0) {
 236 		return TRUE;
 237 	}
 238 	return FALSE;
 239 }
 240 */
 241 
 242 static gboolean
 243 entry_property_maps (RhythmDBPropType prop)
 244 {
 245 	switch (prop) {
 246 		case RHYTHMDB_PROP_TITLE:
 247 		case RHYTHMDB_PROP_MEDIA_TYPE:
 248 		case RHYTHMDB_PROP_FILE_SIZE:
 249 		case RHYTHMDB_PROP_ALBUM:
 250 		case RHYTHMDB_PROP_ARTIST:
 251 		case RHYTHMDB_PROP_YEAR:
 252 		case RHYTHMDB_PROP_GENRE:
 253 		case RHYTHMDB_PROP_DURATION:
 254 		case RHYTHMDB_PROP_BITRATE:
 255 		case RHYTHMDB_PROP_TRACK_NUMBER:
 256 			return TRUE;
 257 
 258 		default:
 259 			return FALSE;
 260 	}
 261 }
 262 
 263 static GVariant *
 264 get_entry_property_value (RhythmDBEntry *entry, const char *property_name)
 265 {
 266 	GVariant *v;
 267 
 268 	if (g_strcmp0 (property_name, "Parent") == 0) {
 269 		return g_variant_new_object_path (RB_MEDIASERVER2_ROOT);
 270 	} else if (g_strcmp0 (property_name, "Type") == 0) {
 271 		return g_variant_new_string ("music");
 272 	} else if (g_strcmp0 (property_name, "Path") == 0) {
 273 		char *path;
 274 
 275 		path = g_strdup_printf (RB_MEDIASERVER2_ENTRY_PREFIX "%lu",
 276 					rhythmdb_entry_get_ulong (entry, RHYTHMDB_PROP_ENTRY_ID));
 277 		v = g_variant_new_string (path);
 278 		g_free (path);
 279 		return v;
 280 	} else if (g_strcmp0 (property_name, "DisplayName") == 0) {
 281 		return g_variant_new_string (rhythmdb_entry_get_string (entry, RHYTHMDB_PROP_TITLE));
 282 	} else if (g_strcmp0 (property_name, "URLs") == 0) {
 283 		const char *urls[] = { NULL, NULL };
 284 		char *url;
 285 		url = rhythmdb_entry_get_playback_uri (entry);
 286 		urls[0] = url;
 287 		v = g_variant_new_strv (urls, -1);
 288 		g_free (url);
 289 		return v;
 290 
 291 	} else if (g_strcmp0 (property_name, "MIMEType") == 0) {
 292 		const char *media_type;
 293 		media_type = rhythmdb_entry_get_string (entry, RHYTHMDB_PROP_MEDIA_TYPE);
 294 		return g_variant_new_string (rb_gst_media_type_to_mime_type (media_type));
 295 	} else if (g_strcmp0 (property_name, "Size") == 0) {
 296 		return g_variant_new_int64 (rhythmdb_entry_get_uint64 (entry, RHYTHMDB_PROP_FILE_SIZE));
 297 	} else if (g_strcmp0 (property_name, "Artist") == 0) {
 298 		return g_variant_new_string (rhythmdb_entry_get_string (entry, RHYTHMDB_PROP_ARTIST));
 299 	} else if (g_strcmp0 (property_name, "Album") == 0) {
 300 		return g_variant_new_string (rhythmdb_entry_get_string (entry, RHYTHMDB_PROP_ALBUM));
 301 	} else if (g_strcmp0 (property_name, "Date") == 0) {
 302 		char *iso8601;
 303 		iso8601 = g_strdup_printf ("%4d-%02d-%02dT%02d:%02d:%02dZ",
 304 					   (int)rhythmdb_entry_get_ulong (entry, RHYTHMDB_PROP_YEAR),
 305 					   1, 1, 0, 0, 0);
 306 		v = g_variant_new_string (iso8601);
 307 		g_free (iso8601);
 308 		return v;
 309 	} else if (g_strcmp0 (property_name, "Genre") == 0) {
 310 		return g_variant_new_string (rhythmdb_entry_get_string (entry, RHYTHMDB_PROP_GENRE));
 311 	} else if (g_strcmp0 (property_name, "Duration") == 0) {
 312 		return g_variant_new_int32 (rhythmdb_entry_get_ulong (entry, RHYTHMDB_PROP_DURATION));
 313 	} else if (g_strcmp0 (property_name, "Bitrate") == 0) {
 314 		return g_variant_new_int32 (rhythmdb_entry_get_ulong (entry, RHYTHMDB_PROP_BITRATE));
 315 	} else if (g_strcmp0 (property_name, "TrackNumber") == 0) {
 316 		return g_variant_new_int32 (rhythmdb_entry_get_ulong (entry, RHYTHMDB_PROP_TRACK_NUMBER));
 317 	}
 318 
 319 	/* not yet: DLNAProfile, AlbumArt */
 320 
 321 	return NULL;
 322 }
 323 
 324 static GVariant *
 325 get_entry_property (GDBusConnection *connection,
 326 		    const char *sender,
 327 		    const char *object_path,
 328 		    const char *interface_name,
 329 		    const char *property_name,
 330 		    GError **error,
 331 		    RBMediaServer2Plugin *plugin)
 332 {
 333 	RhythmDBEntry *entry;
 334 
 335 	rb_debug ("entry property %s", property_name);
 336 	if (g_str_has_prefix (object_path, RB_MEDIASERVER2_ENTRY_PREFIX) == FALSE) {
 337 		g_set_error (error, G_DBUS_ERROR, G_DBUS_ERROR_NOT_SUPPORTED, "no");
 338 		return NULL;
 339 	}
 340 
 341 	entry = rhythmdb_entry_lookup_from_string (plugin->db, object_path + strlen (RB_MEDIASERVER2_ENTRY_PREFIX), TRUE);
 342 	if (entry == NULL) {
 343 		rb_debug ("entry for object path %s not found", object_path);
 344 		g_set_error (error, G_DBUS_ERROR, G_DBUS_ERROR_NOT_SUPPORTED, "no");
 345 		return NULL;
 346 	}
 347 
 348 	return get_entry_property_value (entry, property_name);
 349 }
 350 
 351 static const GDBusInterfaceVTable entry_vtable =
 352 {
 353 	NULL,
 354 	(GDBusInterfaceGetPropertyFunc) get_entry_property,
 355 	NULL
 356 };
 357 
 358 static char **
 359 enumerate_entry_subtree (GDBusConnection *connection,
 360 			 const char *sender,
 361 			 const char *object_path,
 362 			 RBMediaServer2Plugin *plugin)
 363 {
 364 	return (char **)g_new0(char *, 1);
 365 }
 366 
 367 static GDBusInterfaceInfo **
 368 introspect_entry_subtree (GDBusConnection *connection,
 369 			  const char *sender,
 370 			  const char *object_path,
 371 			  const char *node,
 372 			  RBMediaServer2Plugin *plugin)
 373 {
 374 	GPtrArray *p;
 375 	GDBusInterfaceInfo *i;
 376 
 377 	p = g_ptr_array_new ();
 378 
 379 	i = g_dbus_node_info_lookup_interface (plugin->node_info, MEDIA_SERVER2_OBJECT_IFACE_NAME);
 380 	g_ptr_array_add (p, g_dbus_interface_info_ref (i));
 381 
 382 	i = g_dbus_node_info_lookup_interface (plugin->node_info, MEDIA_SERVER2_ITEM_IFACE_NAME);
 383 	g_ptr_array_add (p, g_dbus_interface_info_ref (i));
 384 
 385 	g_ptr_array_add (p, NULL);
 386 
 387 	return (GDBusInterfaceInfo **)g_ptr_array_free (p, FALSE);
 388 }
 389 
 390 static const GDBusInterfaceVTable *
 391 dispatch_entry_subtree (GDBusConnection *connection,
 392 			const char *sender,
 393 			const char *object_path,
 394 			const char *interface_name,
 395 			const char *node,
 396 			gpointer *out_user_data,
 397 			RBMediaServer2Plugin *plugin)
 398 {
 399 	*out_user_data = plugin;
 400 	return &entry_vtable;
 401 }
 402 
 403 static GDBusSubtreeVTable entry_subtree_vtable =
 404 {
 405 	(GDBusSubtreeEnumerateFunc) enumerate_entry_subtree,
 406 	(GDBusSubtreeIntrospectFunc) introspect_entry_subtree,
 407 	(GDBusSubtreeDispatchFunc) dispatch_entry_subtree
 408 };
 409 
 410 /* containers in general */
 411 
 412 static void
 413 emit_updated (GDBusConnection *connection, const char *path)
 414 {
 415 	GError *error = NULL;
 416 	g_dbus_connection_emit_signal (connection,
 417 				       NULL,
 418 				       path,
 419 				       MEDIA_SERVER2_CONTAINER_IFACE_NAME,
 420 				       "Updated",
 421 				       NULL,
 422 				       &error);
 423 	if (error != NULL) {
 424 		g_warning ("Unable to emit Updated signal for MediaServer2 container %s: %s",
 425 			   path,
 426 			   error->message);
 427 		g_clear_error (&error);
 428 	}
 429 }
 430 
 431 static gboolean
 432 emit_container_updated_cb (RBMediaServer2Plugin *plugin)
 433 {
 434 	GList *l, *ll, *lll;
 435 
 436 	rb_debug ("emitting updates");
 437 	/* source containers */
 438 	for (l = plugin->sources; l != NULL; l = l->next) {
 439 		SourceRegistrationData *source_data = l->data;
 440 
 441 		/* property containers */
 442 		for (ll = source_data->properties; ll != NULL; ll = ll->next) {
 443 			SourcePropertyRegistrationData *prop_data = ll->data;
 444 
 445 			/* emit value updates */
 446 			for (lll = prop_data->updated_values; lll != NULL; lll = lll->next) {
 447 				RBRefString *value = lll->data;
 448 				emit_property_value_property_updates (plugin, prop_data, value);
 449 			}
 450 			rb_list_destroy_free (prop_data->updated_values, (GDestroyNotify)rb_refstring_unref);
 451 			prop_data->updated_values = NULL;
 452 
 453 			if (prop_data->updated) {
 454 				emit_updated (plugin->connection, prop_data->dbus_path);
 455 				prop_data->updated = FALSE;
 456 			}
 457 		}
 458 
 459 		if (source_data->updated) {
 460 			emit_source_tracks_property_updates (plugin, source_data);
 461 			if (source_data->flat) {
 462 				emit_updated (plugin->connection, source_data->dbus_path);
 463 			} else {
 464 				char *path;
 465 				path = g_strdup_printf ("%s/all", source_data->dbus_path);
 466 				emit_updated (plugin->connection, path);
 467 				g_free (path);
 468 			}
 469 			source_data->updated = FALSE;
 470 		}
 471 
 472 	}
 473 
 474 	/* source categories */
 475 	for (l = plugin->categories; l != NULL; l = l->next) {
 476 		CategoryRegistrationData *category_data = l->data;
 477 		if (category_data->updated) {
 478 			emit_category_container_property_updates (plugin, category_data);
 479 			emit_updated (plugin->connection, category_data->dbus_path);
 480 			category_data->updated = FALSE;
 481 		}
 482 	}
 483 
 484 	/* root */
 485 	if (plugin->root_updated) {
 486 		emit_root_property_updates (plugin);
 487 		emit_updated (plugin->connection, RB_MEDIASERVER2_ROOT);
 488 		plugin->root_updated = FALSE;
 489 	}
 490 
 491 	rb_debug ("done emitting updates");
 492 	plugin->emit_updated_id = 0;
 493 	return FALSE;
 494 }
 495 
 496 static void
 497 emit_updated_in_idle (RBMediaServer2Plugin *plugin)
 498 {
 499 	if (plugin->emit_updated_id == 0) {
 500 		plugin->emit_updated_id =
 501 			g_idle_add_full (G_PRIORITY_LOW,
 502 					 (GSourceFunc)emit_container_updated_cb,
 503 					 plugin,
 504 					 NULL);
 505 	}
 506 }
 507 
 508 
 509 /* property value source subcontainers (source/year/1995) */
 510 
 511 static char *
 512 encode_property_value (const char *value)
 513 {
 514 	char *encoded;
 515 	const char *hex = "0123456789ABCDEF";
 516 	char *d;
 517 	char c;
 518 
 519 	encoded = g_malloc0 (strlen (value) * 3 + 1);
 520 	d = encoded;
 521 	while (*value != '\0') {
 522 		c = *value++;
 523 		if (g_ascii_isalnum (c)) {
 524 			*d++ = c;
 525 		} else {
 526 			guint8 v = (guint8)c;
 527 			*d++ = '_';
 528 			*d++ = hex[(v >> 4) & 0x0f];
 529 			*d++ = hex[v & 0x0f];
 530 		}
 531 	}
 532 
 533 	return encoded;
 534 }
 535 
 536 #define XDIGIT(c) ((c) <= '9' ? (c) - '0' : ((c) & 0x4F) - 'A' + 10)
 537 #define HEXCHAR(s) ((XDIGIT (s[1]) << 4) + XDIGIT (s[2]))
 538 
 539 static char *
 540 decode_property_value (char *encoded)
 541 {
 542 	char *decoded;
 543 	char *e;
 544 	char *d;
 545 
 546 	decoded = g_malloc0 (strlen (encoded) + 1);
 547 	d = decoded;
 548 	e = encoded;
 549 	while (*e != '\0') {
 550 		if (*e != '_') {
 551 			*d++ = *e++;
 552 		} else if (e[1] != '\0' && e[2] != '\0') {
 553 			*d++ = HEXCHAR(e);
 554 			e += 3;
 555 		} else {
 556 			/* broken */
 557 			break;
 558 		}
 559 	}
 560 
 561 	return decoded;
 562 }
 563 
 564 static char *
 565 extract_property_value (RhythmDB *db, const char *object_path)
 566 {
 567 	char **bits;
 568 	char *value;
 569 	int nbits;
 570 
 571 	bits = g_strsplit (object_path, "/", 0);
 572 	nbits = g_strv_length (bits);
 573 
 574 	value = decode_property_value (bits[nbits-1]);
 575 	g_strfreev (bits);
 576 	return value;
 577 }
 578 
 579 static void
 580 property_value_method_call (GDBusConnection *connection,
 581 			    const char *sender,
 582 			    const char *object_path,
 583 			    const char *interface_name,
 584 			    const char *method_name,
 585 			    GVariant *parameters,
 586 			    GDBusMethodInvocation *invocation,
 587 			    SourcePropertyRegistrationData *data)
 588 {
 589 	GVariantBuilder *list;
 590 	RhythmDB *db;
 591 	char *value;
 592 
 593 	if (g_strcmp0 (interface_name, MEDIA_SERVER2_CONTAINER_IFACE_NAME) != 0) {
 594 		rb_debug ("method call on unexpected interface %s", interface_name);
 595 		return;
 596 	}
 597 
 598 	db = data->source_data->plugin->db;
 599 	value = extract_property_value (db, object_path);
 600 
 601 	if (g_strcmp0 (method_name, "ListChildren") == 0 ||
 602 	    g_strcmp0 (method_name, "ListItems") == 0) {
 603 		RhythmDBQuery *base;
 604 		RhythmDBQuery *query;
 605 		RhythmDBQueryModel *query_model;
 606 		GtkTreeModel *model;
 607 		GtkTreeIter iter;
 608 		guint list_offset;
 609 		guint list_max;
 610 		char **filter;
 611 		guint count = 0;
 612 
 613 		/* consider caching query models? */
 614 		g_object_get (data->source_data->base_query_model, "query", &base, NULL);
 615 		query = rhythmdb_query_copy (base);
 616 
 617 		rhythmdb_query_append (db,
 618 				       query,
 619 				       RHYTHMDB_QUERY_PROP_EQUALS, data->property, value,
 620 				       RHYTHMDB_QUERY_END);
 621 		/* maybe use a result list? */
 622 		query_model = rhythmdb_query_model_new_empty (db);
 623 		rhythmdb_do_full_query_parsed (db, RHYTHMDB_QUERY_RESULTS (query_model), query);
 624 		rhythmdb_query_free (query);
 625 
 626 		g_variant_get (parameters, "(uu^as)", &list_offset, &list_max, &filter);
 627 		list = g_variant_builder_new (G_VARIANT_TYPE ("aa{sv}"));
 628 
 629 		if (rb_str_in_strv ("*", (const char **)filter)) {
 630 			g_strfreev (filter);
 631 			filter = g_strdupv ((char **)all_entry_properties);
 632 		}
 633 
 634 		model = GTK_TREE_MODEL (query_model);
 635 		if (gtk_tree_model_get_iter_first (model, &iter)) {
 636 			do {
 637 				RhythmDBEntry *entry;
 638 				GVariantBuilder *eb;
 639 				int i;
 640 				if (list_max > 0 && count == list_max) {
 641 					break;
 642 				}
 643 
 644 				entry = rhythmdb_query_model_iter_to_entry (query_model, &iter);
 645 				if (entry == NULL) {
 646 					continue;
 647 				}
 648 
 649 				if (list_offset > 0) {
 650 					list_offset--;
 651 					continue;
 652 				}
 653 
 654 				eb = g_variant_builder_new (G_VARIANT_TYPE ("a{sv}"));
 655 				for (i = 0; filter[i] != NULL; i++) {
 656 					GVariant *v;
 657 					v = get_entry_property_value (entry, filter[i]);
 658 					if (v != NULL) {
 659 						g_variant_builder_add (eb, "{sv}", filter[i], v);
 660 					}
 661 				}
 662 
 663 				g_variant_builder_add (list, "a{sv}", eb);
 664 				count++;
 665 
 666 			} while (gtk_tree_model_iter_next (model, &iter));
 667 		}
 668 		g_dbus_method_invocation_return_value (invocation, g_variant_new ("(aa{sv})", list));
 669 		g_variant_builder_unref (list);
 670 
 671 		g_strfreev (filter);
 672 	} else if (g_strcmp0 (method_name, "ListContainers") == 0) {
 673 		list = g_variant_builder_new (G_VARIANT_TYPE ("aa{sv}"));
 674 		g_dbus_method_invocation_return_value (invocation, g_variant_new ("(aa{sv})", list));
 675 		g_variant_builder_unref (list);
 676 	} else if (g_strcmp0 (method_name, "SearchObjects") == 0) {
 677 		g_dbus_method_invocation_return_value (invocation, NULL);
 678 	} else {
 679 		g_dbus_method_invocation_return_error (invocation,
 680 						       G_DBUS_ERROR,
 681 						       G_DBUS_ERROR_NOT_SUPPORTED,
 682 						       "Method %s.%s not supported",
 683 						       interface_name,
 684 						       method_name);
 685 	}
 686 
 687 	g_free (value);
 688 }
 689 
 690 static guint
 691 get_property_value_count (SourcePropertyRegistrationData *data, const char *value)
 692 {
 693 	guint entry_count = 0;
 694 	GtkTreeIter iter;
 695 
 696 	if (rhythmdb_property_model_iter_from_string (data->model, value, &iter)) {
 697 		gtk_tree_model_get (GTK_TREE_MODEL (data->model), &iter,
 698 				    RHYTHMDB_PROPERTY_MODEL_COLUMN_NUMBER, &entry_count,
 699 				    -1);
 700 	}
 701 	return entry_count;
 702 }
 703 
 704 static GVariant *
 705 get_property_value_property (GDBusConnection *connection,
 706 			     const char *sender,
 707 			     const char *object_path,
 708 			     const char *interface_name,
 709 			     const char *property_name,
 710 			     GError **error,
 711 			     SourcePropertyRegistrationData *data)
 712 {
 713 	RhythmDB *db;
 714 	GVariant *v = NULL;
 715 	char *value;
 716 
 717 	db = data->source_data->plugin->db;
 718 	value = extract_property_value (db, object_path);
 719 
 720 	if (g_strcmp0 (interface_name, MEDIA_SERVER2_OBJECT_IFACE_NAME) == 0) {
 721 		if (g_strcmp0 (property_name, "Parent") == 0) {
 722 			v = g_variant_new_object_path (data->dbus_path);
 723 		} else if (g_strcmp0 (property_name, "Type") == 0) {
 724 			v = g_variant_new_string ("container");
 725 		} else if (g_strcmp0 (property_name, "Path") == 0) {
 726 			v = g_variant_new_string (object_path);
 727 		} else if (g_strcmp0 (property_name, "DisplayName") == 0) {
 728 			v = g_variant_new_string (value);
 729 		}
 730 	} else if (g_strcmp0 (interface_name, MEDIA_SERVER2_CONTAINER_IFACE_NAME) == 0) {
 731 
 732 		if (g_strcmp0 (property_name, "ChildCount") == 0 ||
 733 		    g_strcmp0 (property_name, "ItemCount") == 0) {
 734 			v = g_variant_new_uint32 (get_property_value_count (data, value));
 735 		} else if (g_strcmp0 (property_name, "ContainerCount") == 0) {
 736 			v = g_variant_new_uint32 (0);
 737 		} else if (g_strcmp0 (property_name, "Searchable") == 0) {
 738 			v = g_variant_new_boolean (FALSE);
 739 		}
 740 	}
 741 
 742 	if (v == NULL) {
 743 		g_set_error (error,
 744 			     G_DBUS_ERROR,
 745 			     G_DBUS_ERROR_NOT_SUPPORTED,
 746 			     "Property %s.%s not supported",
 747 			     interface_name,
 748 			     property_name);
 749 	}
 750 	g_free (value);
 751 	return v;
 752 }
 753 
 754 static void
 755 emit_property_value_property_updates (RBMediaServer2Plugin *plugin, SourcePropertyRegistrationData *data, RBRefString *value)
 756 {
 757 	GError *error = NULL;
 758 	const char *invalidated[] = { NULL };
 759 	GVariantBuilder *properties;
 760 	GVariant *parameters;
 761 	GVariant *v;
 762 	char *encoded;
 763 	char *path;
 764 
 765 	rb_debug ("updating properties for %s/%s", data->dbus_path, rb_refstring_get (value));
 766 	properties = g_variant_builder_new (G_VARIANT_TYPE ("a{sv}"));
 767 
 768 	v = g_variant_new_uint32 (get_property_value_count (data, rb_refstring_get (value)));
 769 	g_variant_builder_add (properties, "{sv}", "ItemCount", v);
 770 	g_variant_builder_add (properties, "{sv}", "ChildCount", v);
 771 	g_variant_builder_add (properties, "{sv}", "ContainerCount", g_variant_new_uint32 (0));
 772 
 773 	encoded = encode_property_value (rb_refstring_get (value));
 774 	path = g_strdup_printf ("%s/%s", data->dbus_path, encoded);
 775 	g_free (encoded);
 776 
 777 	parameters = g_variant_new ("(sa{sv}^as)",
 778 				    MEDIA_SERVER2_CONTAINER_IFACE_NAME,
 779 				    properties,
 780 				    invalidated);
 781 	g_variant_builder_unref (properties);
 782 	g_dbus_connection_emit_signal (plugin->connection,
 783 				       NULL,
 784 				       path,
 785 				       "org.freedesktop.DBus.Properties",
 786 				       "PropertiesChanged",
 787 				       parameters,
 788 				       &error);
 789 	if (error != NULL) {
 790 		g_warning ("Unable to send property changes for MediaServer2 container %s: %s",
 791 			   path,
 792 			   error->message);
 793 		g_clear_error (&error);
 794 	}
 795 
 796 	emit_updated (plugin->connection, path);
 797 
 798 	g_free (path);
 799 }
 800 
 801 static const GDBusInterfaceVTable property_value_vtable =
 802 {
 803 	(GDBusInterfaceMethodCallFunc) property_value_method_call,
 804 	(GDBusInterfaceGetPropertyFunc) get_property_value_property,
 805 	NULL
 806 };
 807 
 808 /* property-based source subcontainers (source/year/) */
 809 
 810 static void
 811 property_container_method_call (GDBusConnection *connection,
 812 				const char *sender,
 813 				const char *object_path,
 814 				const char *interface_name,
 815 				const char *method_name,
 816 				GVariant *parameters,
 817 				GDBusMethodInvocation *invocation,
 818 				SourcePropertyRegistrationData *data)
 819 {
 820 	GVariantBuilder *list;
 821 
 822 	if (g_strcmp0 (interface_name, MEDIA_SERVER2_CONTAINER_IFACE_NAME) != 0) {
 823 		rb_debug ("method call on unexpected interface %s", interface_name);
 824 		return;
 825 	}
 826 
 827 	if (g_strcmp0 (method_name, "ListChildren") == 0 ||
 828 	    g_strcmp0 (method_name, "ListContainers") == 0) {
 829 		GtkTreeModel *model;
 830 		GtkTreeIter iter;
 831 		guint list_offset;
 832 		guint list_max;
 833 		const char **filter;
 834 		guint count = 0;
 835 		gboolean all_props;
 836 
 837 		g_variant_get (parameters, "(uu^as)", &list_offset, &list_max, &filter);
 838 		list = g_variant_builder_new (G_VARIANT_TYPE ("aa{sv}"));
 839 
 840 		all_props = rb_str_in_strv ("*", filter);
 841 
 842 		model = GTK_TREE_MODEL (data->model);
 843 		if (gtk_tree_model_get_iter_first (model, &iter)) {
 844 			/* skip 'all' row */
 845 			while (gtk_tree_model_iter_next (model, &iter)) {
 846 				char *value;
 847 				guint value_count;
 848 				GVariantBuilder *eb;
 849 				if (list_max > 0 && count == list_max) {
 850 					break;
 851 				}
 852 
 853 				if (list_offset > 0) {
 854 					list_offset--;
 855 					continue;
 856 				}
 857 
 858 				gtk_tree_model_get (model, &iter,
 859 						    RHYTHMDB_PROPERTY_MODEL_COLUMN_TITLE, &value,
 860 						    RHYTHMDB_PROPERTY_MODEL_COLUMN_NUMBER, &value_count,
 861 						    -1);
 862 
 863 				eb = g_variant_builder_new (G_VARIANT_TYPE ("a{sv}"));
 864 				if (all_props || rb_str_in_strv ("Parent", filter)) {
 865 					g_variant_builder_add (eb, "{sv}", "Parent", g_variant_new_object_path (object_path));
 866 				}
 867 				if (all_props || rb_str_in_strv ("Type", filter)) {
 868 					g_variant_builder_add (eb, "{sv}", "Type", g_variant_new_string ("container"));
 869 				}
 870 				if (all_props || rb_str_in_strv ("Path", filter)) {
 871 					char *encoded;
 872 					char *value_path;
 873 					encoded = encode_property_value (value);
 874 					value_path = g_strdup_printf ("%s/%s", object_path, encoded);
 875 					g_variant_builder_add (eb, "{sv}", "Path", g_variant_new_string (value_path));
 876 					g_free (encoded);
 877 					g_free (value_path);
 878 				}
 879 				if (all_props || rb_str_in_strv ("DisplayName", filter)) {
 880 					g_variant_builder_add (eb, "{sv}", "DisplayName", g_variant_new_string (value));
 881 				}
 882 				if (all_props || rb_str_in_strv ("ChildCount", filter)) {
 883 					g_variant_builder_add (eb, "{sv}", "ChildCount", g_variant_new_uint32 (value_count));
 884 				}
 885 				if (all_props || rb_str_in_strv ("ItemCount", filter)) {
 886 					g_variant_builder_add (eb, "{sv}", "ItemCount", g_variant_new_uint32 (value_count));
 887 				}
 888 				if (all_props || rb_str_in_strv ("ContainerCount", filter)) {
 889 					g_variant_builder_add (eb, "{sv}", "ContainerCount", g_variant_new_uint32 (0));
 890 				}
 891 				if (all_props || rb_str_in_strv ("Searchable", filter)) {
 892 					g_variant_builder_add (eb, "{sv}", "Searchable", g_variant_new_boolean (FALSE));
 893 				}
 894 
 895 				g_variant_builder_add (list, "a{sv}", eb);
 896 				g_free (value);
 897 				count++;
 898 			}
 899 		}
 900 		g_dbus_method_invocation_return_value (invocation, g_variant_new ("(aa{sv})", list));
 901 		g_variant_builder_unref (list);
 902 
 903 		g_strfreev ((char **)filter);
 904 	} else if (g_strcmp0 (method_name, "ListItems") == 0) {
 905 		list = g_variant_builder_new (G_VARIANT_TYPE ("aa{sv}"));
 906 		g_dbus_method_invocation_return_value (invocation, g_variant_new ("(aa{sv})", list));
 907 		g_variant_builder_unref (list);
 908 	} else if (g_strcmp0 (method_name, "SearchObjects") == 0) {
 909 		g_dbus_method_invocation_return_value (invocation, NULL);
 910 	} else {
 911 		g_dbus_method_invocation_return_error (invocation,
 912 						       G_DBUS_ERROR,
 913 						       G_DBUS_ERROR_NOT_SUPPORTED,
 914 						       "Method %s.%s not supported",
 915 						       interface_name,
 916 						       method_name);
 917 	}
 918 }
 919 
 920 static GVariant *
 921 get_property_container_property (GDBusConnection *connection,
 922 				 const char *sender,
 923 				 const char *object_path,
 924 				 const char *interface_name,
 925 				 const char *property_name,
 926 				 GError **error,
 927 				 SourcePropertyRegistrationData *data)
 928 {
 929 	if (g_strcmp0 (interface_name, MEDIA_SERVER2_OBJECT_IFACE_NAME) == 0) {
 930 		if (g_strcmp0 (property_name, "Parent") == 0) {
 931 			return g_variant_new_object_path (data->source_data->dbus_path);
 932 		} else if (g_strcmp0 (property_name, "Type") == 0) {
 933 			return g_variant_new_string ("container");
 934 		} else if (g_strcmp0 (property_name, "Path") == 0) {
 935 			return g_variant_new_string (object_path);
 936 		} else if (g_strcmp0 (property_name, "DisplayName") == 0) {
 937 			return g_variant_new_string (data->display_name);
 938 		}
 939 	} else if (g_strcmp0 (interface_name, MEDIA_SERVER2_CONTAINER_IFACE_NAME) == 0) {
 940 
 941 		if (g_strcmp0 (property_name, "ChildCount") == 0 ||
 942 		    g_strcmp0 (property_name, "ContainerCount") == 0) {
 943 			/* don't include the 'all' row */
 944 			guint count;
 945 			count = gtk_tree_model_iter_n_children (GTK_TREE_MODEL (data->model), NULL) - 1;
 946 			return g_variant_new_uint32 (count);
 947 		} else if (g_strcmp0 (property_name, "ItemCount") == 0) {
 948 			return g_variant_new_uint32 (0);
 949 		} else if (g_strcmp0 (property_name, "Searchable") == 0) {
 950 			return g_variant_new_boolean (FALSE);
 951 		}
 952 	}
 953 	g_set_error (error,
 954 		     G_DBUS_ERROR,
 955 		     G_DBUS_ERROR_NOT_SUPPORTED,
 956 		     "Property %s.%s not supported",
 957 		     interface_name,
 958 		     property_name);
 959 	return NULL;
 960 }
 961 
 962 static const GDBusInterfaceVTable property_vtable =
 963 {
 964 	(GDBusInterfaceMethodCallFunc) property_container_method_call,
 965 	(GDBusInterfaceGetPropertyFunc) get_property_container_property,
 966 	NULL
 967 };
 968 
 969 static void
 970 prop_model_row_inserted_cb (GtkTreeModel *model,
 971 			    GtkTreePath *path,
 972 			    GtkTreeIter *iter,
 973 			    SourcePropertyRegistrationData *prop_data)
 974 {
 975 	prop_data->updated = TRUE;
 976 	emit_updated_in_idle (prop_data->source_data->plugin);
 977 }
 978 
 979 static void
 980 prop_model_row_changed_cb (GtkTreeModel *model,
 981 			   GtkTreePath *path,
 982 			   GtkTreeIter *iter,
 983 			   SourcePropertyRegistrationData *prop_data)
 984 {
 985 	char *value;
 986 	RBRefString *refstring;
 987 	gboolean is_all;
 988 	GList *l;
 989 
 990 	gtk_tree_model_get (model, iter,
 991 			    RHYTHMDB_PROPERTY_MODEL_COLUMN_TITLE, &value,
 992 			    RHYTHMDB_PROPERTY_MODEL_COLUMN_PRIORITY, &is_all,
 993 			    -1);
 994 	if (is_all) {
 995 		g_free (value);
 996 		return;
 997 	}
 998 
 999 	refstring = rb_refstring_new (value);
1000 	g_free (value);
1001 
1002 	for (l = prop_data->updated_values; l != NULL; l = l->next) {
1003 		if (refstring == (RBRefString *)l->data) {
1004 			rb_refstring_unref (refstring);
1005 			return;
1006 		}
1007 	}
1008 
1009 	prop_data->updated_values = g_list_prepend (prop_data->updated_values, refstring);
1010 	emit_updated_in_idle (prop_data->source_data->plugin);
1011 }
1012 
1013 static void
1014 prop_model_row_deleted_cb (RhythmDBPropertyModel *prop_model,
1015 			   GtkTreePath *path,
1016 			   SourcePropertyRegistrationData *prop_data)
1017 {
1018 	prop_data->updated = TRUE;
1019 	emit_updated_in_idle (prop_data->source_data->plugin);
1020 }
1021 
1022 static char **
1023 enumerate_property_value_subtree (GDBusConnection *connection,
1024 				  const char *sender,
1025 				  const char *object_path,
1026 				  SourcePropertyRegistrationData *data)
1027 {
1028 	return (char **)g_new0(char *, 1);
1029 }
1030 
1031 static GDBusInterfaceInfo **
1032 introspect_property_value_subtree (GDBusConnection *connection,
1033 				   const char *sender,
1034 				   const char *object_path,
1035 				   const char *node,
1036 				   SourcePropertyRegistrationData *data)
1037 {
1038 	GPtrArray *p;
1039 	GDBusInterfaceInfo *i;
1040 
1041 	p = g_ptr_array_new ();
1042 
1043 	i = g_dbus_node_info_lookup_interface (data->source_data->plugin->node_info, MEDIA_SERVER2_OBJECT_IFACE_NAME);
1044 	g_ptr_array_add (p, g_dbus_interface_info_ref (i));
1045 
1046 	i = g_dbus_node_info_lookup_interface (data->source_data->plugin->node_info, MEDIA_SERVER2_CONTAINER_IFACE_NAME);
1047 	g_ptr_array_add (p, g_dbus_interface_info_ref (i));
1048 
1049 	g_ptr_array_add (p, NULL);
1050 
1051 	return (GDBusInterfaceInfo **)g_ptr_array_free (p, FALSE);
1052 }
1053 
1054 static const GDBusInterfaceVTable *
1055 dispatch_property_value_subtree (GDBusConnection *connection,
1056 				 const char *sender,
1057 				 const char *object_path,
1058 				 const char *interface_name,
1059 				 const char *node,
1060 				 gpointer *out_user_data,
1061 				 SourcePropertyRegistrationData *data)
1062 {
1063 	*out_user_data = data;
1064 	return &property_value_vtable;
1065 }
1066 
1067 static const GDBusSubtreeVTable property_subtree_vtable =
1068 {
1069 	(GDBusSubtreeEnumerateFunc) enumerate_property_value_subtree,
1070 	(GDBusSubtreeIntrospectFunc) introspect_property_value_subtree,
1071 	(GDBusSubtreeDispatchFunc) dispatch_property_value_subtree
1072 };
1073 
1074 static void
1075 register_property_container (GDBusConnection *connection,
1076 			     SourceRegistrationData *source_data,
1077 			     RhythmDBPropType property,
1078 			     const char *display_name)
1079 {
1080 	SourcePropertyRegistrationData *data;
1081 	GDBusInterfaceInfo *iface;
1082 
1083 	data = g_new0 (SourcePropertyRegistrationData, 1);
1084 	data->source_data = source_data;
1085 	data->property = property;
1086 	data->display_name = g_strdup (display_name);
1087 	data->dbus_path = g_strdup_printf ("%s/%s",
1088 					   source_data->dbus_path,
1089 					   rhythmdb_nice_elt_name_from_propid (source_data->plugin->db, property));
1090 
1091 	data->model = rhythmdb_property_model_new (source_data->plugin->db, property);
1092 	g_object_set (data->model, "query-model", source_data->base_query_model, NULL);
1093 	g_signal_connect (data->model, "row-inserted", G_CALLBACK (prop_model_row_inserted_cb), data);
1094 	g_signal_connect (data->model, "row-changed", G_CALLBACK (prop_model_row_changed_cb), data);
1095 	g_signal_connect (data->model, "row-deleted", G_CALLBACK (prop_model_row_deleted_cb), data);
1096 
1097 	data->dbus_subtree_id =
1098 		g_dbus_connection_register_subtree (connection,
1099 						    data->dbus_path,
1100 						    &property_subtree_vtable,
1101 						    G_DBUS_SUBTREE_FLAGS_DISPATCH_TO_UNENUMERATED_NODES,
1102 						    data,
1103 						    NULL,
1104 						    NULL);
1105 
1106 	iface = g_dbus_node_info_lookup_interface (source_data->plugin->node_info, MEDIA_SERVER2_OBJECT_IFACE_NAME);
1107 	data->dbus_object_id[0] =
1108 		g_dbus_connection_register_object (connection,
1109 						   data->dbus_path,
1110 						   iface,
1111 						   &property_vtable,
1112 						   data,
1113 						   NULL,
1114 						   NULL);
1115 
1116 	iface = g_dbus_node_info_lookup_interface (source_data->plugin->node_info, MEDIA_SERVER2_CONTAINER_IFACE_NAME);
1117 	data->dbus_object_id[1] =
1118 		g_dbus_connection_register_object (connection,
1119 						   data->dbus_path,
1120 						   iface,
1121 						   &property_vtable,
1122 						   data,
1123 						   NULL,
1124 						   NULL);
1125 
1126 	source_data->properties = g_list_append (source_data->properties, data);
1127 }
1128 
1129 
1130 /* source containers */
1131 
1132 static void
1133 source_tracks_method_call (GDBusConnection *connection,
1134 			   const char *sender,
1135 			   const char *object_path,
1136 			   const char *interface_name,
1137 			   const char *method_name,
1138 			   GVariant *parameters,
1139 			   GDBusMethodInvocation *invocation,
1140 			   SourceRegistrationData *source_data)
1141 {
1142 	GVariantBuilder *list;
1143 
1144 	if (g_strcmp0 (interface_name, MEDIA_SERVER2_CONTAINER_IFACE_NAME) != 0) {
1145 		rb_debug ("method call on unexpected interface %s", interface_name);
1146 		return;
1147 	}
1148 
1149 	if (g_strcmp0 (method_name, "ListChildren") == 0 ||
1150 	    g_strcmp0 (method_name, "ListItems") == 0) {
1151 		GtkTreeModel *model;
1152 		GtkTreeIter iter;
1153 		guint list_offset;
1154 		guint list_max;
1155 		char **filter;
1156 		guint count = 0;
1157 
1158 		g_variant_get (parameters, "(uu^as)", &list_offset, &list_max, &filter);
1159 		list = g_variant_builder_new (G_VARIANT_TYPE ("aa{sv}"));
1160 
1161 		if (rb_str_in_strv ("*", (const char **)filter)) {
1162 			g_strfreev (filter);
1163 			filter = g_strdupv ((char **)all_entry_properties);
1164 		}
1165 
1166 		model = GTK_TREE_MODEL (source_data->base_query_model);
1167 		if (gtk_tree_model_get_iter_first (model, &iter)) {
1168 			do {
1169 				RhythmDBEntry *entry;
1170 				GVariantBuilder *eb;
1171 				int i;
1172 				if (list_max > 0 && count == list_max) {
1173 					break;
1174 				}
1175 
1176 				entry = rhythmdb_query_model_iter_to_entry (source_data->base_query_model, &iter);
1177 				if (entry == NULL) {
1178 					continue;
1179 				}
1180 
1181 				if (list_offset > 0) {
1182 					list_offset--;
1183 					continue;
1184 				}
1185 
1186 				eb = g_variant_builder_new (G_VARIANT_TYPE ("a{sv}"));
1187 				for (i = 0; filter[i] != NULL; i++) {
1188 					GVariant *v;
1189 					v = get_entry_property_value (entry, filter[i]);
1190 					if (v != NULL) {
1191 						g_variant_builder_add (eb, "{sv}", filter[i], v);
1192 					}
1193 				}
1194 
1195 				g_variant_builder_add (list, "a{sv}", eb);
1196 				count++;
1197 
1198 			} while (gtk_tree_model_iter_next (model, &iter));
1199 		}
1200 		g_dbus_method_invocation_return_value (invocation, g_variant_new ("(aa{sv})", list));
1201 		g_variant_builder_unref (list);
1202 
1203 		g_strfreev (filter);
1204 	} else if (g_strcmp0 (method_name, "ListContainers") == 0) {
1205 		list = g_variant_builder_new (G_VARIANT_TYPE ("aa{sv}"));
1206 		g_dbus_method_invocation_return_value (invocation, g_variant_new ("(aa{sv})", list));
1207 		g_variant_builder_unref (list);
1208 	} else if (g_strcmp0 (method_name, "SearchObjects") == 0) {
1209 		g_dbus_method_invocation_return_value (invocation, NULL);
1210 	} else {
1211 		g_dbus_method_invocation_return_error (invocation,
1212 						       G_DBUS_ERROR,
1213 						       G_DBUS_ERROR_NOT_SUPPORTED,
1214 						       "Method %s.%s not supported",
1215 						       interface_name,
1216 						       method_name);
1217 	}
1218 }
1219 
1220 static GVariant *
1221 get_source_tracks_property (GDBusConnection *connection,
1222 			    const char *sender,
1223 			    const char *object_path,
1224 			    const char *interface_name,
1225 			    const char *property_name,
1226 			    GError **error,
1227 			    SourceRegistrationData *source_data)
1228 {
1229 	GVariant *v;
1230 	char *name;
1231 
1232 	if (g_strcmp0 (interface_name, MEDIA_SERVER2_OBJECT_IFACE_NAME) == 0) {
1233 		if (g_strcmp0 (property_name, "Parent") == 0) {
1234 			if (source_data->flat) {
1235 				return g_variant_new_object_path (source_data->parent_dbus_path);
1236 			} else {
1237 				return g_variant_new_object_path (source_data->dbus_path);
1238 			}
1239 		} else if (g_strcmp0 (property_name, "Type") == 0) {
1240 			return g_variant_new_string ("container");
1241 		} else if (g_strcmp0 (property_name, "Path") == 0) {
1242 			return g_variant_new_string (object_path);
1243 		} else if (g_strcmp0 (property_name, "DisplayName") == 0) {
1244 			if (source_data->flat) {
1245 				g_object_get (source_data->source, "name", &name, NULL);
1246 				v = g_variant_new_string (name);
1247 				g_free (name);
1248 				return v;
1249 			} else {
1250 				return g_variant_new_string (_("All Tracks"));
1251 			}
1252 		}
1253 	} else if (g_strcmp0 (interface_name, MEDIA_SERVER2_CONTAINER_IFACE_NAME) == 0) {
1254 
1255 		if (g_strcmp0 (property_name, "ChildCount") == 0 ||
1256 		    g_strcmp0 (property_name, "ItemCount") == 0) {
1257 			int count;
1258 			count = gtk_tree_model_iter_n_children (GTK_TREE_MODEL (source_data->base_query_model), NULL);
1259 			return g_variant_new_uint32 (count);
1260 		} else if (g_strcmp0 (property_name, "ContainerCount") == 0) {
1261 			return g_variant_new_uint32 (0);
1262 		} else if (g_strcmp0 (property_name, "Searchable") == 0) {
1263 			return g_variant_new_boolean (FALSE);
1264 		}
1265 	}
1266 	g_set_error (error,
1267 		     G_DBUS_ERROR,
1268 		     G_DBUS_ERROR_NOT_SUPPORTED,
1269 		     "Property %s.%s not supported",
1270 		     interface_name,
1271 		     property_name);
1272 	return NULL;
1273 }
1274 
1275 static void
1276 add_source_tracks_property (RBMediaServer2Plugin *plugin, GVariantBuilder *properties, const char *iface, const char *property, SourceRegistrationData *source_data)
1277 {
1278 	GVariant *v;
1279 	v = get_source_tracks_property (plugin->connection, NULL, source_data->dbus_path, iface, property, NULL, source_data);
1280 	g_variant_builder_add (properties, "{sv}", property, v);
1281 }
1282 
1283 static void
1284 emit_source_tracks_property_updates (RBMediaServer2Plugin *plugin, SourceRegistrationData *source_data)
1285 {
1286 	GError *error = NULL;
1287 	const char *invalidated[] = { NULL };
1288 	GVariantBuilder *properties;
1289 	GVariant *parameters;
1290 	char *path;
1291 
1292 	rb_debug ("updating properties for source %s", source_data->dbus_path);
1293 	properties = g_variant_builder_new (G_VARIANT_TYPE ("a{sv}"));
1294 	add_source_tracks_property (plugin, properties, MEDIA_SERVER2_CONTAINER_IFACE_NAME, "ItemCount", source_data);
1295 	add_source_tracks_property (plugin, properties, MEDIA_SERVER2_CONTAINER_IFACE_NAME, "ChildCount", source_data);
1296 	add_source_tracks_property (plugin, properties, MEDIA_SERVER2_CONTAINER_IFACE_NAME, "ContainerCount", source_data);
1297 
1298 	parameters = g_variant_new ("(sa{sv}^as)",
1299 				    MEDIA_SERVER2_CONTAINER_IFACE_NAME,
1300 				    properties,
1301 				    invalidated);
1302 	g_variant_builder_unref (properties);
1303 	if (source_data->flat) {
1304 		path = g_strdup (source_data->dbus_path);
1305 	} else {
1306 		path = g_strdup_printf ("%s/all", source_data->dbus_path);
1307 	}
1308 	g_dbus_connection_emit_signal (plugin->connection,
1309 				       NULL,
1310 				       path,
1311 				       "org.freedesktop.DBus.Properties",
1312 				       "PropertiesChanged",
1313 				       parameters,
1314 				       &error);
1315 	g_free (path);
1316 	if (error != NULL) {
1317 		g_warning ("Unable to send property changes for MediaServer2 container %s: %s",
1318 			   source_data->dbus_path,
1319 			   error->message);
1320 		g_clear_error (&error);
1321 	}
1322 }
1323 
1324 static const GDBusInterfaceVTable source_tracks_vtable =
1325 {
1326 	(GDBusInterfaceMethodCallFunc) source_tracks_method_call,
1327 	(GDBusInterfaceGetPropertyFunc) get_source_tracks_property,
1328 	NULL
1329 };
1330 
1331 
1332 static void
1333 source_properties_method_call (GDBusConnection *connection,
1334 			       const char *sender,
1335 			       const char *object_path,
1336 			       const char *interface_name,
1337 			       const char *method_name,
1338 			       GVariant *parameters,
1339 			       GDBusMethodInvocation *invocation,
1340 			       SourceRegistrationData *source_data)
1341 {
1342 	GVariantBuilder *list;
1343 	guint value_count;
1344 
1345 	if (g_strcmp0 (interface_name, MEDIA_SERVER2_CONTAINER_IFACE_NAME) != 0) {
1346 		rb_debug ("method call on unexpected interface %s", interface_name);
1347 		return;
1348 	}
1349 
1350 	if (g_strcmp0 (method_name, "ListChildren") == 0 ||
1351 	    g_strcmp0 (method_name, "ListContainers") == 0) {
1352 		GList *l;
1353 		guint list_offset;
1354 		guint list_max;
1355 		const char **filter;
1356 		guint count = 0;
1357 		gboolean all_props;
1358 		GVariantBuilder *eb;
1359 
1360 		g_variant_get (parameters, "(uu^as)", &list_offset, &list_max, &filter);
1361 		list = g_variant_builder_new (G_VARIANT_TYPE ("aa{sv}"));
1362 
1363 		all_props = rb_str_in_strv ("*", filter);
1364 
1365 		/* 'all tracks' container */
1366 		if (list_offset == 0) {
1367 			eb = g_variant_builder_new (G_VARIANT_TYPE ("a{sv}"));
1368 			value_count = gtk_tree_model_iter_n_children (GTK_TREE_MODEL (source_data->base_query_model), NULL);
1369 			if (all_props || rb_str_in_strv ("Parent", filter)) {
1370 				g_variant_builder_add (eb, "{sv}", "Parent", g_variant_new_object_path (object_path));
1371 			}
1372 			if (all_props || rb_str_in_strv ("Type", filter)) {
1373 				g_variant_builder_add (eb, "{sv}", "Type", g_variant_new_string ("container"));
1374 			}
1375 			if (all_props || rb_str_in_strv ("Path", filter)) {
1376 				char *path;
1377 				path = g_strdup_printf ("%s/all", object_path);
1378 				g_variant_builder_add (eb, "{sv}", "Path", g_variant_new_string (path));
1379 				g_free (path);
1380 			}
1381 			if (all_props || rb_str_in_strv ("DisplayName", filter)) {
1382 				g_variant_builder_add (eb, "{sv}", "DisplayName", g_variant_new_string (_("All Tracks")));
1383 			}
1384 			if (all_props || rb_str_in_strv ("ChildCount", filter)) {
1385 				g_variant_builder_add (eb, "{sv}", "ChildCount", g_variant_new_uint32 (value_count));
1386 			}
1387 			if (all_props || rb_str_in_strv ("ItemCount", filter)) {
1388 				g_variant_builder_add (eb, "{sv}", "ItemCount", g_variant_new_uint32 (value_count));
1389 			}
1390 			if (all_props || rb_str_in_strv ("ContainerCount", filter)) {
1391 				g_variant_builder_add (eb, "{sv}", "ContainerCount", g_variant_new_uint32 (0));
1392 			}
1393 			if (all_props || rb_str_in_strv ("Searchable", filter)) {
1394 				g_variant_builder_add (eb, "{sv}", "Searchable", g_variant_new_boolean (FALSE));
1395 			}
1396 
1397 			g_variant_builder_add (list, "a{sv}", eb);
1398 			count++;
1399 		} else {
1400 			list_offset--;
1401 		}
1402 
1403 		/* property-based containers */
1404 		for (l = source_data->properties; l != NULL; l = l->next) {
1405 			SourcePropertyRegistrationData *prop_data = l->data;
1406 			if (list_max > 0 && count == list_max) {
1407 				break;
1408 			}
1409 
1410 			if (list_offset > 0) {
1411 				list_offset--;
1412 				continue;
1413 			}
1414 
1415 			value_count = gtk_tree_model_iter_n_children (GTK_TREE_MODEL (prop_data->model), NULL) - 1;
1416 
1417 			eb = g_variant_builder_new (G_VARIANT_TYPE ("a{sv}"));
1418 			if (all_props || rb_str_in_strv ("Parent", filter)) {
1419 				g_variant_builder_add (eb, "{sv}", "Parent", g_variant_new_object_path (object_path));
1420 			}
1421 			if (all_props || rb_str_in_strv ("Type", filter)) {
1422 				g_variant_builder_add (eb, "{sv}", "Type", g_variant_new_string ("container"));
1423 			}
1424 			if (all_props || rb_str_in_strv ("Path", filter)) {
1425 				g_variant_builder_add (eb, "{sv}", "Path", g_variant_new_string (prop_data->dbus_path));
1426 			}
1427 			if (all_props || rb_str_in_strv ("DisplayName", filter)) {
1428 				g_variant_builder_add (eb, "{sv}", "DisplayName", g_variant_new_string (prop_data->display_name));
1429 			}
1430 			if (all_props || rb_str_in_strv ("ChildCount", filter)) {
1431 				g_variant_builder_add (eb, "{sv}", "ChildCount", g_variant_new_uint32 (value_count));
1432 			}
1433 			if (all_props || rb_str_in_strv ("ItemCount", filter)) {
1434 				g_variant_builder_add (eb, "{sv}", "ItemCount", g_variant_new_uint32 (0));
1435 			}
1436 			if (all_props || rb_str_in_strv ("ContainerCount", filter)) {
1437 				g_variant_builder_add (eb, "{sv}", "ContainerCount", g_variant_new_uint32 (value_count));
1438 			}
1439 			if (all_props || rb_str_in_strv ("Searchable", filter)) {
1440 				g_variant_builder_add (eb, "{sv}", "Searchable", g_variant_new_boolean (FALSE));
1441 			}
1442 
1443 			g_variant_builder_add (list, "a{sv}", eb);
1444 			count++;
1445 		}
1446 
1447 		g_dbus_method_invocation_return_value (invocation, g_variant_new ("(aa{sv})", list));
1448 		g_variant_builder_unref (list);
1449 
1450 		g_strfreev ((char **)filter);
1451 	} else if (g_strcmp0 (method_name, "ListItems") == 0) {
1452 		list = g_variant_builder_new (G_VARIANT_TYPE ("aa{sv}"));
1453 		g_dbus_method_invocation_return_value (invocation, g_variant_new ("(aa{sv})", list));
1454 		g_variant_builder_unref (list);
1455 	} else if (g_strcmp0 (method_name, "SearchObjects") == 0) {
1456 		g_dbus_method_invocation_return_value (invocation, NULL);
1457 	} else {
1458 		g_dbus_method_invocation_return_error (invocation,
1459 						       G_DBUS_ERROR,
1460 						       G_DBUS_ERROR_NOT_SUPPORTED,
1461 						       "Method %s.%s not supported",
1462 						       interface_name,
1463 						       method_name);
1464 	}
1465 }
1466 
1467 static GVariant *
1468 get_source_properties_property (GDBusConnection *connection,
1469 				const char *sender,
1470 				const char *object_path,
1471 				const char *interface_name,
1472 				const char *property_name,
1473 				GError **error,
1474 				SourceRegistrationData *source_data)
1475 {
1476 	GVariant *v;
1477 	char *name;
1478 
1479 	if (g_strcmp0 (interface_name, MEDIA_SERVER2_OBJECT_IFACE_NAME) == 0) {
1480 		if (g_strcmp0 (property_name, "Parent") == 0) {
1481 			return g_variant_new_object_path (source_data->parent_dbus_path);
1482 		} else if (g_strcmp0 (property_name, "Type") == 0) {
1483 			return g_variant_new_string ("container");
1484 		} else if (g_strcmp0 (property_name, "Path") == 0) {
1485 			return g_variant_new_string (object_path);
1486 		} else if (g_strcmp0 (property_name, "DisplayName") == 0) {
1487 			g_object_get (source_data->source, "name", &name, NULL);
1488 			v = g_variant_new_string (name);
1489 			g_free (name);
1490 			return v;
1491 		}
1492 	} else if (g_strcmp0 (interface_name, MEDIA_SERVER2_CONTAINER_IFACE_NAME) == 0) {
1493 
1494 		if (g_strcmp0 (property_name, "ChildCount") == 0 ||
1495 		    g_strcmp0 (property_name, "ContainerCount") == 0) {
1496 			return g_variant_new_uint32 (g_list_length (source_data->properties) + 1);
1497 		} else if (g_strcmp0 (property_name, "ItemCount") == 0) {
1498 			return g_variant_new_uint32 (0);
1499 		} else if (g_strcmp0 (property_name, "Searchable") == 0) {
1500 			return g_variant_new_boolean (FALSE);
1501 		}
1502 	}
1503 	g_set_error (error,
1504 		     G_DBUS_ERROR,
1505 		     G_DBUS_ERROR_NOT_SUPPORTED,
1506 		     "Property %s.%s not supported",
1507 		     interface_name,
1508 		     property_name);
1509 	return NULL;
1510 }
1511 
1512 static const GDBusInterfaceVTable source_properties_vtable =
1513 {
1514 	(GDBusInterfaceMethodCallFunc) source_properties_method_call,
1515 	(GDBusInterfaceGetPropertyFunc) get_source_properties_property,
1516 	NULL
1517 };
1518 
1519 /* source container registration */
1520 
1521 static void
1522 add_source_container (GVariantBuilder *list, SourceRegistrationData *source_data, const char **filter)
1523 {
1524 	GVariantBuilder *i;
1525 	gboolean all_props;
1526 
1527 	i = g_variant_builder_new (G_VARIANT_TYPE ("a{sv}"));
1528 	all_props = rb_str_in_strv ("*", filter);
1529 
1530 
1531 	if (all_props || rb_str_in_strv ("Parent", filter)) {
1532 		g_variant_builder_add (i, "{sv}", "Parent", g_variant_new_object_path (source_data->parent_dbus_path));
1533 	}
1534 	if (all_props || rb_str_in_strv ("Type", filter)) {
1535 		g_variant_builder_add (i, "{sv}", "Type", g_variant_new_string ("container"));
1536 	}
1537 	if (all_props || rb_str_in_strv ("Path", filter)) {
1538 		g_variant_builder_add (i, "{sv}", "Path", g_variant_new_string (source_data->dbus_path));
1539 	}
1540 	if (all_props || rb_str_in_strv ("DisplayName", filter)) {
1541 		char *name;
1542 		g_object_get (source_data->source, "name", &name, NULL);
1543 		g_variant_builder_add (i, "{sv}", "DisplayName", g_variant_new_string (name));
1544 		g_free (name);
1545 	}
1546 	if (source_data->flat) {
1547 		int entry_count;
1548 		entry_count = gtk_tree_model_iter_n_children (GTK_TREE_MODEL (source_data->base_query_model), NULL);
1549 		if (all_props || rb_str_in_strv ("ChildCount", filter)) {
1550 			g_variant_builder_add (i, "{sv}", "ChildCount", g_variant_new_uint32 (entry_count));
1551 		}
1552 		if (all_props || rb_str_in_strv ("ItemCount", filter)) {
1553 			g_variant_builder_add (i, "{sv}", "ItemCount", g_variant_new_uint32 (entry_count));
1554 		}
1555 		if (all_props || rb_str_in_strv ("ContainerCount", filter)) {
1556 			g_variant_builder_add (i, "{sv}", "ContainerCount", g_variant_new_uint32 (0));
1557 		}
1558 	} else {
1559 		int child_count = g_list_length (source_data->properties) + 1;
1560 		if (all_props || rb_str_in_strv ("ChildCount", filter)) {
1561 			g_variant_builder_add (i, "{sv}", "ChildCount", g_variant_new_uint32 (child_count));
1562 		}
1563 		if (all_props || rb_str_in_strv ("ContainerCount", filter)) {
1564 			g_variant_builder_add (i, "{sv}", "ContainerCount", g_variant_new_uint32 (child_count));
1565 		}
1566 		if (all_props || rb_str_in_strv ("ItemCount", filter)) {
1567 			g_variant_builder_add (i, "{sv}", "ItemCount", g_variant_new_uint32 (0));
1568 		}
1569 	}
1570 	if (all_props || rb_str_in_strv ("Searchable", filter)) {
1571 		g_variant_builder_add (i, "{sv}", "Searchable", g_variant_new_boolean (FALSE));
1572 	}
1573 
1574 	g_variant_builder_add (list, "a{sv}", i);
1575 }
1576 
1577 static int
1578 count_sources_by_parent (RBMediaServer2Plugin *plugin, const char *parent_dbus_path)
1579 {
1580 	GList *l;
1581 	int count = 0;
1582 	for (l = plugin->sources; l != NULL; l = l->next) {
1583 		SourceRegistrationData *source_data;
1584 		source_data = l->data;
1585 		if (g_strcmp0 (source_data->parent_dbus_path, parent_dbus_path) == 0) {
1586 			count++;
1587 		}
1588 	}
1589 	return count;
1590 }
1591 
1592 static void
1593 list_sources_by_parent (RBMediaServer2Plugin *plugin,
1594 			GVariantBuilder *list,
1595 			const char *parent_dbus_path,
1596 			guint *list_offset,
1597 			guint *list_count,
1598 			guint list_max,
1599 			const char **filter)
1600 {
1601 	GList *l;
1602 	for (l = plugin->sources; l != NULL; l = l->next) {
1603 		SourceRegistrationData *source_data;
1604 		if (list_max > 0 && (*list_count) == list_max) {
1605 			break;
1606 		}
1607 
1608 		source_data = l->data;
1609 		if (g_strcmp0 (source_data->parent_dbus_path, parent_dbus_path) != 0) {
1610 			continue;
1611 		}
1612 
1613 		if ((*list_offset) > 0) {
1614 			(*list_offset)--;
1615 			continue;
1616 		}
1617 
1618 		add_source_container (list, source_data, filter);
1619 		(*list_count)++;
1620 	}
1621 }
1622 
1623 static SourceRegistrationData *
1624 find_registration_data (RBMediaServer2Plugin *plugin, RBSource *source)
1625 {
1626 	GList *l;
1627 	for (l = plugin->sources; l != NULL; l = l->next) {
1628 		SourceRegistrationData *data = l->data;
1629 		if (data->source == source) {
1630 			return data;
1631 		}
1632 	}
1633 	return NULL;
1634 }
1635 
1636 static void
1637 destroy_registration_data (SourceRegistrationData *source_data)
1638 {
1639 	g_free (source_data->dbus_path);
1640 	g_free (source_data->parent_dbus_path);
1641 	g_object_unref (source_data->source);
1642 	g_object_unref (source_data->base_query_model);
1643 
1644 	g_free (source_data);
1645 }
1646 
1647 static void
1648 source_parent_updated (SourceRegistrationData *source_data)
1649 {
1650 	GList *l;
1651 	for (l = source_data->plugin->categories; l != NULL; l = l->next) {
1652 		CategoryRegistrationData *category_data = l->data;
1653 		if (g_strcmp0 (source_data->parent_dbus_path, category_data->dbus_path) == 0) {
1654 			category_data->updated = TRUE;
1655 			break;
1656 		}
1657 	}
1658 	if (l == NULL) {
1659 		source_data->plugin->root_updated = TRUE;
1660 	}
1661 
1662 	emit_updated_in_idle (source_data->plugin);
1663 }
1664 
1665 static void
1666 source_updated (SourceRegistrationData *source_data)
1667 {
1668 	source_data->updated = TRUE;
1669 	emit_updated_in_idle (source_data->plugin);
1670 }
1671 
1672 /* signal handlers for source container updates */
1673 
1674 static void
1675 row_inserted_cb (GtkTreeModel *model, GtkTreePath *path, GtkTreeIter *iter, SourceRegistrationData *source_data)
1676 {
1677 	source_updated (source_data);
1678 }
1679 
1680 static void
1681 row_deleted_cb (GtkTreeModel *model, GtkTreePath *path, SourceRegistrationData *source_data)
1682 {
1683 	source_updated (source_data);
1684 }
1685 
1686 static void
1687 entry_prop_changed_cb (RhythmDBQueryModel *model,
1688 		       RhythmDBEntry *entry,
1689 		       RhythmDBPropType prop,
1690 		       const GValue *old,
1691 		       const GValue *new_value,
1692 		       SourceRegistrationData *source_data)
1693 {
1694 	GList *l;
1695 
1696 	if (entry_property_maps (prop) == FALSE) {
1697 		return;
1698 	}
1699 
1700 	source_updated (source_data);
1701 	for (l = source_data->properties; l != NULL; l = l->next) {
1702 		SourcePropertyRegistrationData *prop_data = l->data;
1703 		RBRefString *value;
1704 
1705 		/* property model signal handlers will take care of this */
1706 		if (prop == prop_data->property)
1707 			continue;
1708 
1709 		prop_data->updated = TRUE;
1710 		value = rhythmdb_entry_get_refstring (entry, prop_data->property);
1711 		if (g_list_find (prop_data->updated_values, value) == NULL) {
1712 			prop_data->updated_values =
1713 				g_list_prepend (prop_data->updated_values, value);
1714 		}
1715 	}
1716 }
1717 
1718 static void
1719 disconnect_query_model_signals (SourceRegistrationData *source_data)
1720 {
1721 	g_signal_handlers_disconnect_by_func (source_data->base_query_model, row_inserted_cb, source_data);
1722 	g_signal_handlers_disconnect_by_func (source_data->base_query_model, entry_prop_changed_cb, source_data);
1723 	g_signal_handlers_disconnect_by_func (source_data->base_query_model, row_deleted_cb, source_data);
1724 }
1725 
1726 static void
1727 connect_query_model_signals (SourceRegistrationData *source_data)
1728 {
1729 	g_signal_connect (source_data->base_query_model, "row-inserted", G_CALLBACK (row_inserted_cb), source_data);
1730 	g_signal_connect (source_data->base_query_model, "entry-prop-changed", G_CALLBACK (entry_prop_changed_cb), source_data);
1731 	g_signal_connect (source_data->base_query_model, "row-deleted", G_CALLBACK (row_deleted_cb), source_data);
1732 }
1733 
1734 static void
1735 base_query_model_updated_cb (RBSource *source, GParamSpec *pspec, SourceRegistrationData *source_data)
1736 {
1737 	GList *l;
1738 
1739 	if (source_data->base_query_model != NULL) {
1740 		disconnect_query_model_signals (source_data);
1741 		g_object_unref (source_data->base_query_model);
1742 	}
1743 
1744 	g_object_get (source, "base-query-model", &source_data->base_query_model, NULL);
1745 	connect_query_model_signals (source_data);
1746 
1747 	for (l = source_data->properties; l != NULL; l = l->next) {
1748 		SourcePropertyRegistrationData *prop_data = l->data;
1749 		g_object_set (prop_data->model, "query-model", source_data->base_query_model, NULL);
1750 	}
1751 
1752 	source_updated (source_data);
1753 }
1754 
1755 static void
1756 name_updated_cb (RBSource *source, GParamSpec *pspec, SourceRegistrationData *source_data)
1757 {
1758 	source_updated (source_data);
1759 }
1760 
1761 static void
1762 source_deleted_cb (RBDisplayPage *page, RBMediaServer2Plugin *plugin)
1763 {
1764 	SourceRegistrationData *source_data;
1765 
1766 	source_data = find_registration_data (plugin, RB_SOURCE (page));
1767 	if (source_data != NULL) {
1768 		rb_debug ("source for container %s deleted", source_data->dbus_path);
1769 		unregister_source_container (plugin, source_data, FALSE);
1770 	}
1771 }
1772 
1773 
1774 static SourceRegistrationData *
1775 register_source_container (RBMediaServer2Plugin *plugin,
1776 			   RBSource *source,
1777 			   const char *dbus_path,
1778 			   const char *parent_dbus_path,
1779 			   gboolean flat)
1780 {
1781 	SourceRegistrationData *source_data;
1782 	GDBusInterfaceInfo *container_iface;
1783 
1784 	source_data = g_new0 (SourceRegistrationData, 1);
1785 	source_data->source = g_object_ref (source);
1786 	source_data->dbus_path = g_strdup (dbus_path);
1787 	source_data->parent_dbus_path = g_strdup (parent_dbus_path);
1788 	source_data->plugin = plugin;
1789 	source_data->flat = flat;
1790 
1791 	container_iface = g_dbus_node_info_lookup_interface (plugin->node_info, MEDIA_SERVER2_CONTAINER_IFACE_NAME);
1792 	if (flat) {
1793 		register_object (plugin, &source_tracks_vtable, container_iface, dbus_path, source_data, source_data->dbus_reg_id);
1794 	} else {
1795 		char *tracks_path;
1796 
1797 		register_object (plugin, &source_properties_vtable, container_iface, dbus_path, source_data, source_data->dbus_reg_id);
1798 
1799 		tracks_path = g_strdup_printf ("%s/all", dbus_path);
1800 		register_object (plugin, &source_tracks_vtable, container_iface, tracks_path, source_data, source_data->all_tracks_reg_id);
1801 	}
1802 
1803 	g_object_get (source, "base-query-model", &source_data->base_query_model, NULL);
1804 	connect_query_model_signals (source_data);
1805 	g_signal_connect (source, "notify::base-query-model", G_CALLBACK (base_query_model_updated_cb), source_data);
1806 	g_signal_connect (source, "notify::name", G_CALLBACK (name_updated_cb), source_data);
1807 	g_signal_connect (source, "deleted", G_CALLBACK (source_deleted_cb), plugin);
1808 
1809 	/* add to registration list */
1810 	plugin->sources = g_list_append (plugin->sources, source_data);
1811 
1812 	/* emit 'updated' signal on parent container */
1813 	g_dbus_connection_emit_signal (plugin->connection, NULL, parent_dbus_path, MEDIA_SERVER2_CONTAINER_IFACE_NAME, "Updated", NULL, NULL);
1814 
1815 	return source_data;
1816 }
1817 
1818 static void
1819 unregister_source_container (RBMediaServer2Plugin *plugin, SourceRegistrationData *source_data, gboolean deactivating)
1820 {
1821 	/* if object registration ids exist, unregister the object */
1822 	unregister_object (plugin, source_data->dbus_reg_id);
1823 
1824 	/* remove signal handlers */
1825 	disconnect_query_model_signals (source_data);
1826 	g_signal_handlers_disconnect_by_func (source_data->source, G_CALLBACK (base_query_model_updated_cb), source_data);
1827 	g_signal_handlers_disconnect_by_func (source_data->source, G_CALLBACK (name_updated_cb), source_data);
1828 
1829 	if (deactivating == FALSE) {
1830 		/* remove from registration list */
1831 		plugin->sources = g_list_remove (plugin->sources, source_data);
1832 
1833 		/* emit 'updated' signal on parent container */
1834 		source_parent_updated (source_data);
1835 		destroy_registration_data (source_data);
1836 	}
1837 }
1838 
1839 /* source category containers */
1840 
1841 static void
1842 add_category_container (GVariantBuilder *list, CategoryRegistrationData *data, const char **filter)
1843 {
1844 	GVariantBuilder *i;
1845 	int source_count;
1846 	gboolean all_props;
1847 
1848 	i = g_variant_builder_new (G_VARIANT_TYPE ("a{sv}"));
1849 	all_props = rb_str_in_strv ("*", filter);
1850 
1851 	source_count = count_sources_by_parent (data->plugin, data->dbus_path);
1852 
1853 	if (all_props || rb_str_in_strv ("Parent", filter)) {
1854 		g_variant_builder_add (i, "{sv}", "Parent", g_variant_new_object_path (data->parent_dbus_path));
1855 	}
1856 	if (all_props || rb_str_in_strv ("Type", filter)) {
1857 		g_variant_builder_add (i, "{sv}", "Type", g_variant_new_string ("container"));
1858 	}
1859 	if (all_props || rb_str_in_strv ("Path", filter)) {
1860 		g_variant_builder_add (i, "{sv}", "Path", g_variant_new_string (data->dbus_path));
1861 	}
1862 	if (all_props || rb_str_in_strv ("DisplayName", filter)) {
1863 		g_variant_builder_add (i, "{sv}", "DisplayName", g_variant_new_string (data->name));
1864 	}
1865 	if (all_props || rb_str_in_strv ("ChildCount", filter)) {
1866 		g_variant_builder_add (i, "{sv}", "ChildCount", g_variant_new_uint32 (source_count));
1867 	}
1868 	if (all_props || rb_str_in_strv ("ItemCount", filter)) {
1869 		g_variant_builder_add (i, "{sv}", "ItemCount", g_variant_new_uint32 (0));
1870 	}
1871 	if (all_props || rb_str_in_strv ("ContainerCount", filter)) {
1872 		g_variant_builder_add (i, "{sv}", "ContainerCount", g_variant_new_uint32 (source_count));
1873 	}
1874 	if (all_props || rb_str_in_strv ("Searchable", filter)) {
1875 		g_variant_builder_add (i, "{sv}", "Searchable", g_variant_new_boolean (FALSE));
1876 	}
1877 
1878 	g_variant_builder_add (list, "a{sv}", i);
1879 }
1880 
1881 static void
1882 list_categories_by_parent (RBMediaServer2Plugin *plugin,
1883 			   GVariantBuilder *list,
1884 			   const char *parent_dbus_path,
1885 			   guint *list_offset,
1886 			   guint *list_count,
1887 			   guint list_max,
1888 			   const char **filter)
1889 {
1890 	GList *l;
1891 	for (l = plugin->categories; l != NULL; l = l->next) {
1892 		CategoryRegistrationData *category_data;
1893 		if (list_max > 0 && (*list_count) == list_max) {
1894 			break;
1895 		}
1896 
1897 		category_data = l->data;
1898 		if (g_strcmp0 (category_data->parent_dbus_path, parent_dbus_path) != 0) {
1899 			continue;
1900 		}
1901 
1902 		if ((*list_offset) > 0) {
1903 			(*list_offset)--;
1904 			continue;
1905 		}
1906 
1907 		add_category_container (list, category_data, filter);
1908 		(*list_count)++;
1909 	}
1910 }
1911 
1912 static int
1913 count_categories_by_parent (RBMediaServer2Plugin *plugin, const char *parent_dbus_path)
1914 {
1915 	GList *l;
1916 	int count = 0;
1917 	for (l = plugin->categories; l != NULL; l = l->next) {
1918 		CategoryRegistrationData *category_data;
1919 		category_data = l->data;
1920 		if (g_strcmp0 (category_data->parent_dbus_path, parent_dbus_path) == 0) {
1921 			count++;
1922 		}
1923 	}
1924 	return count;
1925 }
1926 
1927 
1928 static void
1929 category_container_method_call (GDBusConnection *connection,
1930 				const char *sender,
1931 				const char *object_path,
1932 				const char *interface_name,
1933 				const char *method_name,
1934 				GVariant *parameters,
1935 				GDBusMethodInvocation *invocation,
1936 				CategoryRegistrationData *data)
1937 {
1938 	if (g_strcmp0 (interface_name, MEDIA_SERVER2_CONTAINER_IFACE_NAME) == 0) {
1939 		guint list_offset;
1940 		guint list_max;
1941 		guint list_count = 0;
1942 		const char **filter;
1943 		GVariantBuilder *list;
1944 
1945 		if (g_strcmp0 (method_name, "ListChildren") == 0 ||
1946 		    g_strcmp0 (method_name, "ListContainers") == 0) {
1947 			g_variant_get (parameters, "(uu^as)", &list_offset, &list_max, &filter);
1948 			rb_debug ("listing containers (%s) - offset %d, max %d", method_name, list_offset, list_max);
1949 
1950 			list = g_variant_builder_new (G_VARIANT_TYPE ("aa{sv}"));
1951 			list_sources_by_parent (data->plugin, list, object_path, &list_offset, &list_count, list_max, filter);
1952 			rb_debug ("returned %d containers", list_count);
1953 
1954 			g_dbus_method_invocation_return_value (invocation, g_variant_new ("(aa{sv})", list));
1955 			g_variant_builder_unref (list);
1956 			g_strfreev ((char **)filter);
1957 		} else if (g_strcmp0 (method_name, "ListItems") == 0) {
1958 			rb_debug ("listing items");
1959 			g_variant_get (parameters, "(uu^as)", &list_offset, &list_max, &filter);
1960 			list = g_variant_builder_new (G_VARIANT_TYPE ("aa{sv}"));
1961 			g_dbus_method_invocation_return_value (invocation, g_variant_new ("(aa{sv})", list));
1962 			g_variant_builder_unref (list);
1963 			g_strfreev ((char **)filter);
1964 		} else if (g_strcmp0 (method_name, "SearchObjects") == 0) {
1965 			rb_debug ("search not supported");
1966 			g_dbus_method_invocation_return_value (invocation, NULL);
1967 		}
1968 	} else {
1969 		g_dbus_method_invocation_return_error (invocation,
1970 						       G_DBUS_ERROR,
1971 						       G_DBUS_ERROR_NOT_SUPPORTED,
1972 						       "Method %s.%s not supported",
1973 						       interface_name,
1974 						       method_name);
1975 	}
1976 }
1977 
1978 static GVariant *
1979 get_category_container_property (GDBusConnection *connection,
1980 				 const char *sender,
1981 				 const char *object_path,
1982 				 const char *interface_name,
1983 				 const char *property_name,
1984 				 GError **error,
1985 				 CategoryRegistrationData *data)
1986 {
1987 	int count;
1988 	if (g_strcmp0 (interface_name, MEDIA_SERVER2_OBJECT_IFACE_NAME) == 0) {
1989 		if (g_strcmp0 (property_name, "Parent") == 0) {
1990 			return g_variant_new_object_path (data->parent_dbus_path);
1991 		} else if (g_strcmp0 (property_name, "Type") == 0) {
1992 			return g_variant_new_string ("container");
1993 		} else if (g_strcmp0 (property_name, "Path") == 0) {
1994 			return g_variant_new_string (object_path);
1995 		} else if (g_strcmp0 (property_name, "DisplayName") == 0) {
1996 			return g_variant_new_string (data->name);
1997 		}
1998 	} else if (g_strcmp0 (interface_name, MEDIA_SERVER2_CONTAINER_IFACE_NAME) == 0) {
1999 		if (g_strcmp0 (property_name, "ChildCount") == 0 ||
2000 		    g_strcmp0 (property_name, "ContainerCount") == 0) {
2001 			count = count_sources_by_parent (data->plugin, object_path);
2002 			rb_debug ("child/container count %d", count);
2003 			return g_variant_new_uint32 (count);
2004 		} else if (g_strcmp0 (property_name, "ItemCount") == 0) {
2005 			return g_variant_new_uint32 (0);
2006 		} else if (g_strcmp0 (property_name, "Searchable") == 0) {
2007 			return g_variant_new_boolean (FALSE);
2008 		}
2009 	}
2010 	g_set_error (error,
2011 		     G_DBUS_ERROR,
2012 		     G_DBUS_ERROR_NOT_SUPPORTED,
2013 		     "Property %s.%s not supported",
2014 		     interface_name,
2015 		     property_name);
2016 	return NULL;
2017 }
2018 
2019 static void
2020 add_category_container_property (RBMediaServer2Plugin *plugin, GVariantBuilder *properties, const char *iface, const char *property, CategoryRegistrationData *category_data)
2021 {
2022 	GVariant *v;
2023 	v = get_category_container_property (plugin->connection, NULL, category_data->dbus_path, iface, property, NULL, category_data);
2024 	g_variant_builder_add (properties, "{sv}", property, v);
2025 }
2026 
2027 static void
2028 emit_category_container_property_updates (RBMediaServer2Plugin *plugin, CategoryRegistrationData *category_data)
2029 {
2030 	GError *error = NULL;
2031 	const char *invalidated[] = { NULL };
2032 	GVariantBuilder *properties;
2033 	GVariant *parameters;
2034 
2035 	rb_debug ("updating properties for category %s", category_data->dbus_path);
2036 	properties = g_variant_builder_new (G_VARIANT_TYPE ("a{sv}"));
2037 	add_category_container_property (plugin, properties, MEDIA_SERVER2_CONTAINER_IFACE_NAME, "ItemCount", category_data);
2038 	add_category_container_property (plugin, properties, MEDIA_SERVER2_CONTAINER_IFACE_NAME, "ChildCount", category_data);
2039 	add_category_container_property (plugin, properties, MEDIA_SERVER2_CONTAINER_IFACE_NAME, "ContainerCount", category_data);
2040 
2041 	parameters = g_variant_new ("(sa{sv}^as)",
2042 				    MEDIA_SERVER2_CONTAINER_IFACE_NAME,
2043 				    properties,
2044 				    invalidated);
2045 	g_variant_builder_unref (properties);
2046 	g_dbus_connection_emit_signal (plugin->connection,
2047 				       NULL,
2048 				       category_data->dbus_path,
2049 				       "org.freedesktop.DBus.Properties",
2050 				       "PropertiesChanged",
2051 				       parameters,
2052 				       &error);
2053 	if (error != NULL) {
2054 		g_warning ("Unable to send property changes for MediaServer2 container %s: %s",
2055 			   category_data->dbus_path,
2056 			   error->message);
2057 		g_clear_error (&error);
2058 	}
2059 }
2060 
2061 
2062 static const GDBusInterfaceVTable category_container_vtable =
2063 {
2064 	(GDBusInterfaceMethodCallFunc) category_container_method_call,
2065 	(GDBusInterfaceGetPropertyFunc) get_category_container_property,
2066 	NULL
2067 };
2068 
2069 static void
2070 register_category_container (RBMediaServer2Plugin *plugin,
2071 			     const char *name,
2072 			     const char *dbus_path,
2073 			     const char *parent_dbus_path,
2074 			     gboolean (*match_source) (RBSource *source))
2075 {
2076 	CategoryRegistrationData *category_data;
2077 	GDBusInterfaceInfo *container_iface;
2078 
2079 	category_data = g_new0 (CategoryRegistrationData, 1);
2080 	category_data->name = g_strdup (name);
2081 	category_data->dbus_path = g_strdup (dbus_path);
2082 	category_data->parent_dbus_path = g_strdup (parent_dbus_path);
2083 	category_data->plugin = plugin;
2084 	category_data->match_source = match_source;
2085 
2086 	container_iface = g_dbus_node_info_lookup_interface (plugin->node_info, MEDIA_SERVER2_CONTAINER_IFACE_NAME);
2087 	register_object (plugin, &category_container_vtable, container_iface, dbus_path, category_data, category_data->dbus_reg_id);
2088 
2089 	/* add to registration list */
2090 	plugin->categories = g_list_append (plugin->categories, category_data);
2091 
2092 	/* emit 'updated' signal on parent container */
2093 	g_dbus_connection_emit_signal (plugin->connection, NULL, parent_dbus_path, MEDIA_SERVER2_CONTAINER_IFACE_NAME, "Updated", NULL, NULL);
2094 }
2095 
2096 static void
2097 destroy_category_data (CategoryRegistrationData *category_data)
2098 {
2099 	g_free (category_data->name);
2100 	g_free (category_data->dbus_path);
2101 	g_free (category_data->parent_dbus_path);
2102 	g_free (category_data);
2103 }
2104 
2105 static void
2106 unregister_category_container (RBMediaServer2Plugin *plugin, CategoryRegistrationData *category_data, gboolean deactivating)
2107 {
2108 	/* if object registration ids exist, unregister the object */
2109 	unregister_object (plugin, category_data->dbus_reg_id);
2110 
2111 	if (deactivating == FALSE) {
2112 		/* remove from registration list */
2113 		plugin->categories = g_list_remove (plugin->categories, category_data);
2114 
2115 		/* emit 'updated' signal on parent container */
2116 		g_dbus_connection_emit_signal (plugin->connection, NULL, category_data->parent_dbus_path, MEDIA_SERVER2_CONTAINER_IFACE_NAME, "Updated", NULL, NULL);
2117 
2118 		destroy_category_data (category_data);
2119 	}
2120 }
2121 
2122 /* root container */
2123 
2124 static void
2125 root_method_call (GDBusConnection *connection,
2126 		  const char *sender,
2127 		  const char *object_path,
2128 		  const char *interface_name,
2129 		  const char *method_name,
2130 		  GVariant *parameters,
2131 		  GDBusMethodInvocation *invocation,
2132 		  RBMediaServer2Plugin *plugin)
2133 {
2134 	if (g_strcmp0 (interface_name, MEDIA_SERVER2_CONTAINER_IFACE_NAME) == 0) {
2135 		guint list_offset;
2136 		guint list_max;
2137 		guint list_count = 0;
2138 		const char **filter;
2139 		GVariantBuilder *list;
2140 
2141 		if (g_strcmp0 (method_name, "ListChildren") == 0 ||
2142 		    g_strcmp0 (method_name, "ListContainers") == 0) {
2143 			rb_debug ("listing containers (%s)", method_name);
2144 			g_variant_get (parameters, "(uu^as)", &list_offset, &list_max, &filter);
2145 
2146 			list = g_variant_builder_new (G_VARIANT_TYPE ("aa{sv}"));
2147 			list_sources_by_parent (plugin, list, object_path, &list_offset, &list_count, list_max, filter);
2148 			list_categories_by_parent (plugin, list, object_path, &list_offset, &list_count, list_max, filter);
2149 
2150 			g_dbus_method_invocation_return_value (invocation, g_variant_new ("(aa{sv})", list));
2151 			g_variant_builder_unref (list);
2152 			g_strfreev ((char **)filter);
2153 		} else if (g_strcmp0 (method_name, "ListItems") == 0) {
2154 			rb_debug ("listing items");
2155 			g_variant_get (parameters, "(uu^as)", &list_offset, &list_max, &filter);
2156 			list = g_variant_builder_new (G_VARIANT_TYPE ("aa{sv}"));
2157 			g_dbus_method_invocation_return_value (invocation, g_variant_new ("(aa{sv})", list));
2158 			g_variant_builder_unref (list);
2159 			g_strfreev ((char **)filter);
2160 		} else if (g_strcmp0 (method_name, "SearchObjects") == 0) {
2161 			rb_debug ("search not supported");
2162 			g_dbus_method_invocation_return_value (invocation, NULL);
2163 		}
2164 	} else {
2165 		g_dbus_method_invocation_return_error (invocation,
2166 						       G_DBUS_ERROR,
2167 						       G_DBUS_ERROR_NOT_SUPPORTED,
2168 						       "Method %s.%s not supported",
2169 						       interface_name,
2170 						       method_name);
2171 	}
2172 }
2173 
2174 static GVariant *
2175 get_root_property (GDBusConnection *connection,
2176 		   const char *sender,
2177 		   const char *object_path,
2178 		   const char *interface_name,
2179 		   const char *property_name,
2180 		   GError **error,
2181 		   RBMediaServer2Plugin *plugin)
2182 {
2183 	GVariant *v;
2184 	int count;
2185 	if (g_strcmp0 (interface_name, MEDIA_SERVER2_OBJECT_IFACE_NAME) == 0) {
2186 		if (g_strcmp0 (property_name, "Parent") == 0) {
2187 			return g_variant_new_object_path (object_path);
2188 		} else if (g_strcmp0 (property_name, "Type") == 0) {
2189 			return g_variant_new_string ("container");
2190 		} else if (g_strcmp0 (property_name, "Path") == 0) {
2191 			return g_variant_new_string (object_path);
2192 		} else if (g_strcmp0 (property_name, "DisplayName") == 0) {
2193 			char *share_name = g_settings_get_string (plugin->settings, "share-name");
2194 			if (share_name == NULL || share_name[0] == '\0') {
2195 				g_free (share_name);
2196 				share_name = g_strdup ("@REALNAME@'s Rhythmbox on @HOSTNAME@");
2197 			}
2198 			v = g_variant_new_string (share_name);
2199 			g_free (share_name);
2200 			return v;
2201 		}
2202 	} else if (g_strcmp0 (interface_name, MEDIA_SERVER2_CONTAINER_IFACE_NAME) == 0) {
2203 		if (g_strcmp0 (property_name, "ChildCount") == 0 ||
2204 		    g_strcmp0 (property_name, "ContainerCount") == 0) {
2205 			count = count_sources_by_parent (plugin, object_path);
2206 			count += count_categories_by_parent (plugin, object_path);
2207 			return g_variant_new_uint32 (count);
2208 		} else if (g_strcmp0 (property_name, "ItemCount") == 0) {
2209 			return g_variant_new_uint32 (0);
2210 		} else if (g_strcmp0 (property_name, "Searchable") == 0) {
2211 			return g_variant_new_boolean (FALSE);
2212 		} else if (g_strcmp0 (property_name, "Icon") == 0) {
2213 			/* XXX implement this, I guess */
2214 		}
2215 	}
2216 	g_set_error (error,
2217 		     G_DBUS_ERROR,
2218 		     G_DBUS_ERROR_NOT_SUPPORTED,
2219 		     "Property %s.%s not supported",
2220 		     interface_name,
2221 		     property_name);
2222 	return NULL;
2223 }
2224 
2225 static void
2226 add_root_property (RBMediaServer2Plugin *plugin, GVariantBuilder *properties, const char *iface, const char *property)
2227 {
2228 	GVariant *v;
2229 	v = get_root_property (plugin->connection, NULL, RB_MEDIASERVER2_ROOT, iface, property, NULL, plugin);
2230 	g_variant_builder_add (properties, "{sv}", property, v);
2231 }
2232 
2233 static void
2234 emit_root_property_updates (RBMediaServer2Plugin *plugin)
2235 {
2236 	GError *error = NULL;
2237 	const char *invalidated[] = { NULL };
2238 	GVariantBuilder *properties;
2239 	GVariant *parameters;
2240 
2241 
2242 	properties = g_variant_builder_new (G_VARIANT_TYPE ("a{sv}"));
2243 	add_root_property (plugin, properties, MEDIA_SERVER2_CONTAINER_IFACE_NAME, "ItemCount");
2244 	add_root_property (plugin, properties, MEDIA_SERVER2_CONTAINER_IFACE_NAME, "ChildCount");
2245 	add_root_property (plugin, properties, MEDIA_SERVER2_CONTAINER_IFACE_NAME, "ContainerCount");
2246 
2247 	parameters = g_variant_new ("(sa{sv}^as)",
2248 				    MEDIA_SERVER2_CONTAINER_IFACE_NAME,
2249 				    properties,
2250 				    invalidated);
2251 	g_variant_builder_unref (properties);
2252 	g_dbus_connection_emit_signal (plugin->connection,
2253 				       NULL,
2254 				       RB_MEDIASERVER2_ROOT,
2255 				       "org.freedesktop.DBus.Properties",
2256 				       "PropertiesChanged",
2257 				       parameters,
2258 				       &error);
2259 	if (error != NULL) {
2260 		g_warning ("Unable to send property changes for MediaServer2 root container: %s",
2261 			   error->message);
2262 		g_clear_error (&error);
2263 	}
2264 }
2265 
2266 static const GDBusInterfaceVTable root_vtable =
2267 {
2268 	(GDBusInterfaceMethodCallFunc) root_method_call,
2269 	(GDBusInterfaceGetPropertyFunc) get_root_property,
2270 	NULL
2271 };
2272 
2273 /* source watching */
2274 
2275 static gboolean
2276 is_shareable_playlist (RBSource *source)
2277 {
2278 	gboolean is_local;
2279 
2280 	if (RB_IS_PLAYLIST_SOURCE (source) == FALSE) {
2281 		return FALSE;
2282 	}
2283 
2284 	g_object_get (source, "is-local", &is_local, NULL);
2285 	return is_local;
2286 }
2287 
2288 /*
2289  * exposing a device source over dbus only makes sense once it's fully populated.
2290  * we don't currently have a way to determine that, so we don't share devices yet.
2291  */
2292 /*
2293 static gboolean
2294 is_shareable_device (RBSource *source)
2295 {
2296 	return RB_IS_DEVICE_SOURCE (source);
2297 }
2298 */
2299 
2300 static void
2301 display_page_inserted_cb (RBDisplayPageModel *model, RBDisplayPage *page, GtkTreeIter *iter, RBMediaServer2Plugin *plugin)
2302 {
2303 	GList *l;
2304 
2305 	if (RB_IS_SOURCE (page)) {
2306 		RBSource *source = RB_SOURCE (page);
2307 		/* figure out if this is a source we can publish */
2308 		for (l = plugin->categories; l != NULL; l = l->next) {
2309 			CategoryRegistrationData *category_data = l->data;
2310 
2311 			if (category_data->match_source (source)) {
2312 				char *dbus_path;
2313 				dbus_path = g_strdup_printf ("%s/%" G_GINTPTR_FORMAT,
2314 							     category_data->dbus_path,
2315 							     (gintptr) source);
2316 				rb_debug ("adding new source %s to category %s", dbus_path, category_data->name);
2317 				register_source_container (plugin, source, dbus_path, category_data->dbus_path, TRUE);
2318 				g_free (dbus_path);
2319 			}
2320 		}
2321 	}
2322 
2323 }
2324 
2325 static gboolean
2326 display_page_foreach_cb (GtkTreeModel *model, GtkTreePath *path, GtkTreeIter *iter, RBMediaServer2Plugin *plugin)
2327 {
2328 	RBDisplayPage *page;
2329 
2330 	gtk_tree_model_get (model, iter,
2331 			    RB_DISPLAY_PAGE_MODEL_COLUMN_PAGE, &page,
2332 			    -1);
2333 	display_page_inserted_cb (RB_DISPLAY_PAGE_MODEL (model), page, iter, plugin);
2334 
2335 	g_object_unref (page);
2336 	return FALSE;
2337 }
2338 
2339 /* plugin */
2340 
2341 static void
2342 name_acquired_cb (GDBusConnection *connection, const char *name, RBMediaServer2Plugin *plugin)
2343 {
2344 	rb_debug ("acquired dbus name %s", name);
2345 }
2346 
2347 static void
2348 name_lost_cb (GDBusConnection *connection, const char *name, RBMediaServer2Plugin *plugin)
2349 {
2350 	rb_debug ("lost dbus name %s", name);
2351 }
2352 
2353 static void
2354 impl_activate (PeasActivatable *bplugin)
2355 {
2356 	RBMediaServer2Plugin *plugin;
2357 	GDBusInterfaceInfo *container_iface;
2358 	SourceRegistrationData *source_data;
2359 	RBSource *source;
2360 	GError *error = NULL;
2361 	RBShell *shell;
2362 
2363 	rb_debug ("activating DBus MediaServer2 plugin");
2364 
2365 	plugin = RB_DBUS_MEDIA_SERVER_PLUGIN (bplugin);
2366 	g_object_get (plugin, "object", &shell, NULL);
2367 	g_object_get (shell,
2368 		      "db", &plugin->db,
2369 		      "display-page-model", &plugin->display_page_model,
2370 		      NULL);
2371 
2372 	plugin->settings = g_settings_new ("org.gnome.rhythmbox.sharing");
2373 
2374 	plugin->node_info = g_dbus_node_info_new_for_xml (media_server2_spec, &error);
2375 	if (error != NULL) {
2376 		g_warning ("Unable to parse MediaServer2 spec: %s", error->message);
2377 		g_clear_error (&error);
2378 		g_object_unref (shell);
2379 		return;
2380 	}
2381 
2382 	plugin->connection = g_bus_get_sync (G_BUS_TYPE_SESSION, NULL, &error);
2383 	if (error != NULL) {
2384 		g_warning ("Unable to connect to D-Bus: %s", error->message);
2385 		g_clear_error (&error);
2386 		g_object_unref (shell);
2387 		return;
2388 	}
2389 
2390 	container_iface = g_dbus_node_info_lookup_interface (plugin->node_info, MEDIA_SERVER2_CONTAINER_IFACE_NAME);
2391 
2392 	/* register root */
2393 	register_object (plugin, &root_vtable, container_iface, RB_MEDIASERVER2_ROOT, G_OBJECT (plugin), plugin->root_reg_id);
2394 
2395 	/* register fixed sources (library, podcasts, etc.) */
2396 	g_object_get (shell, "library-source", &source, NULL);
2397 	source_data = register_source_container (plugin, source, RB_MEDIASERVER2_LIBRARY, RB_MEDIASERVER2_ROOT, FALSE);
2398 	register_property_container (plugin->connection, source_data, RHYTHMDB_PROP_ARTIST, _("Artists"));
2399 	register_property_container (plugin->connection, source_data, RHYTHMDB_PROP_ALBUM, _("Albums"));
2400 	register_property_container (plugin->connection, source_data, RHYTHMDB_PROP_GENRE, _("Genres"));
2401 	/* year won't work yet */
2402 	g_object_unref (source);
2403 
2404 	/* watch for user-creatable sources (playlists, devices) */
2405 	g_signal_connect_object (plugin->display_page_model, "page-inserted", G_CALLBACK (display_page_inserted_cb), plugin, 0);
2406 	gtk_tree_model_foreach (GTK_TREE_MODEL (plugin->display_page_model),
2407 				(GtkTreeModelForeachFunc) display_page_foreach_cb,
2408 				plugin);
2409 	register_category_container (plugin, _("Playlists"), RB_MEDIASERVER2_PLAYLISTS, RB_MEDIASERVER2_ROOT, is_shareable_playlist);
2410 	/* see comments above */
2411 	/* register_category_container (plugin, _("Devices"), RB_MEDIASERVER2_DEVICES, RB_MEDIASERVER2_ROOT, is_shareable_device); */
2412 
2413 	/* register entry subtree */
2414 	plugin->entry_reg_id = g_dbus_connection_register_subtree (plugin->connection,
2415 								   RB_MEDIASERVER2_ENTRY_SUBTREE,
2416 								   &entry_subtree_vtable,
2417 								   G_DBUS_SUBTREE_FLAGS_DISPATCH_TO_UNENUMERATED_NODES,
2418 								   plugin,
2419 								   NULL,
2420 								   &error);
2421 	if (error != NULL) {
2422 		g_warning ("Unable to register MediaServer2 entry subtree: %s", error->message);
2423 		g_clear_error (&error);
2424 		g_object_unref (shell);
2425 		return;
2426 	}
2427 
2428 	plugin->name_own_id = g_bus_own_name (G_BUS_TYPE_SESSION,
2429 					      RB_MEDIASERVER2_BUS_NAME,
2430 					      G_BUS_NAME_OWNER_FLAGS_NONE,
2431 					      NULL,
2432 					      (GBusNameAcquiredCallback) name_acquired_cb,
2433 					      (GBusNameLostCallback) name_lost_cb,
2434 					      g_object_ref (plugin),
2435 					      g_object_unref);
2436 	g_object_unref (shell);
2437 }
2438 
2439 static void
2440 impl_deactivate	(PeasActivatable *bplugin)
2441 {
2442 	RBMediaServer2Plugin *plugin;
2443 	GList *l;
2444 
2445 	plugin = RB_DBUS_MEDIA_SERVER_PLUGIN (bplugin);
2446 
2447 	if (plugin->emit_updated_id != 0) {
2448 		g_source_remove (plugin->emit_updated_id);
2449 		plugin->emit_updated_id = 0;
2450 	}
2451 
2452 	/* unregister objects */
2453 	unregister_object (plugin, plugin->root_reg_id);
2454 
2455 	for (l = plugin->sources; l != NULL; l = l->next) {
2456 		unregister_source_container (plugin, l->data, TRUE);
2457 	}
2458 	rb_list_destroy_free (plugin->sources, (GDestroyNotify) destroy_registration_data);
2459 	plugin->sources = NULL;
2460 
2461 	for (l = plugin->categories; l != NULL; l = l->next) {
2462 		unregister_category_container (plugin, l->data, TRUE);
2463 	}
2464 	rb_list_destroy_free (plugin->categories, (GDestroyNotify) destroy_category_data);
2465 	plugin->categories = NULL;
2466 
2467 	if (plugin->entry_reg_id != 0) {
2468 		g_dbus_connection_unregister_subtree (plugin->connection, plugin->entry_reg_id);
2469 		plugin->entry_reg_id = 0;
2470 	}
2471 
2472 	if (plugin->settings != NULL) {
2473 		g_object_unref (plugin->settings);
2474 		plugin->settings = NULL;
2475 	}
2476 
2477 	if (plugin->display_page_model != NULL) {
2478 		g_signal_handlers_disconnect_by_func (plugin->display_page_model,
2479 						      display_page_inserted_cb,
2480 						      plugin);
2481 		g_object_unref (plugin->display_page_model);
2482 		plugin->display_page_model = NULL;
2483 	}
2484 
2485 	if (plugin->db != NULL) {
2486 		g_object_unref (plugin->db);
2487 		plugin->db = NULL;
2488 	}
2489 
2490 	if (plugin->name_own_id > 0) {
2491 		g_bus_unown_name (plugin->name_own_id);
2492 		plugin->name_own_id = 0;
2493 	}
2494 
2495 	if (plugin->connection != NULL) {
2496 		g_object_unref (plugin->connection);
2497 		plugin->connection = NULL;
2498 	}
2499 }
2500 
2501 
2502 G_MODULE_EXPORT void
2503 peas_register_types (PeasObjectModule *module)
2504 {
2505 	rb_dbus_media_server_plugin_register_type (G_TYPE_MODULE (module));
2506 	peas_object_module_register_extension_type (module,
2507 						    PEAS_TYPE_ACTIVATABLE,
2508 						    RB_TYPE_DBUS_MEDIA_SERVER_PLUGIN);
2509 }