hythmbox-2.98/sources/rb-media-player-source.c

No issues found

Incomplete coverage

Tool Failure ID Location Function Message Data
clang-analyzer no-output-found rb-media-player-source.c Message(text='Unable to locate XML output from invoke-clang-analyzer') None
clang-analyzer no-output-found rb-media-player-source.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
Failure running clang-analyzer ('no-output-found')
Message
Unable to locate XML output from invoke-clang-analyzer
  1 /*
  2  *  Copyright (C) 2009 Paul Bellamy  <paul.a.bellamy@gmail.com>
  3  *  Copyright (C) 2010 Jonathan Matthew  <jonathan@d14n.org>
  4  *
  5  *  This program is free software; you can redistribute it and/or modify
  6  *  it under the terms of the GNU General Public License as published by
  7  *  the Free Software Foundation; either version 2 of the License, or
  8  *  (at your option) any later version.
  9  *
 10  *  The Rhythmbox authors hereby grant permission for non-GPL compatible
 11  *  GStreamer plugins to be used and distributed together with GStreamer
 12  *  and Rhythmbox. This permission is above and beyond the permissions granted
 13  *  by the GPL license by which Rhythmbox is covered. If you modify this code
 14  *  you may extend this exception to your version of the code, but you are not
 15  *  obligated to do so. If you do not wish to do so, delete this exception
 16  *  statement from your version.
 17  *
 18  *  This program is distributed in the hope that it will be useful,
 19  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 20  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 21  *  GNU General Public License for more details.
 22  *
 23  *  You should have received a copy of the GNU General Public License
 24  *  along with this program; if not, write to the Free Software
 25  *  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301  USA.
 26  *
 27  */
 28 
 29 #include "config.h"
 30 
 31 #include <glib.h>
 32 #include <glib/gi18n.h>
 33 #include <glib/gstdio.h>
 34 #include <string.h>
 35 
 36 #include "rb-shell.h"
 37 #include "rb-media-player-source.h"
 38 #include "rb-transfer-target.h"
 39 #include "rb-sync-settings.h"
 40 #include "rb-sync-settings-ui.h"
 41 #include "rb-sync-state.h"
 42 #include "rb-sync-state-ui.h"
 43 #include "rb-dialog.h"
 44 #include "rb-debug.h"
 45 #include "rb-file-helpers.h"
 46 #include "rb-builder-helpers.h"
 47 #include "rb-playlist-manager.h"
 48 #include "rb-util.h"
 49 
 50 typedef struct {
 51 	RBSyncSettings *sync_settings;
 52 
 53 	GtkActionGroup *action_group;
 54 	GtkAction *sync_action;
 55 
 56 	/* properties dialog bits */
 57 	GtkDialog *properties_dialog;
 58 	RBSyncBarData volume_usage;
 59 
 60 	/* sync settings dialog bits */
 61 	GtkWidget *sync_dialog;
 62 	GtkWidget *sync_dialog_label;
 63 	GtkWidget *sync_dialog_error_box;
 64 	guint sync_dialog_update_id;
 65 
 66 	/* sync state */
 67 	RBSyncState *sync_state;
 68 
 69 	GstEncodingTarget *encoding_target;
 70 } RBMediaPlayerSourcePrivate;
 71 
 72 G_DEFINE_TYPE (RBMediaPlayerSource, rb_media_player_source, RB_TYPE_BROWSER_SOURCE);
 73 
 74 #define MEDIA_PLAYER_SOURCE_GET_PRIVATE(o)   (G_TYPE_INSTANCE_GET_PRIVATE ((o), RB_TYPE_MEDIA_PLAYER_SOURCE, RBMediaPlayerSourcePrivate))
 75 
 76 static void rb_media_player_source_class_init (RBMediaPlayerSourceClass *klass);
 77 static void rb_media_player_source_init (RBMediaPlayerSource *source);
 78 static void rb_media_player_source_dispose (GObject *object);
 79 
 80 static void rb_media_player_source_set_property (GObject *object,
 81 					 guint prop_id,
 82 					 const GValue *value,
 83 					 GParamSpec *pspec);
 84 static void rb_media_player_source_get_property (GObject *object,
 85 					 guint prop_id,
 86 					 GValue *value,
 87 					 GParamSpec *pspec);
 88 static void rb_media_player_source_constructed (GObject *object);
 89 
 90 static void sync_cmd (GtkAction *action, RBSource *source);
 91 static gboolean sync_idle_delete_entries (RBMediaPlayerSource *source);
 92 
 93 static gboolean impl_receive_drag (RBDisplayPage *page, GtkSelectionData *data);
 94 static void impl_delete_thyself (RBDisplayPage *page);
 95 
 96 static char *impl_get_delete_action (RBSource *source);
 97 
 98 static GtkActionEntry rb_media_player_source_actions[] = {
 99 	{ "MediaPlayerSourceSync", GTK_STOCK_REFRESH, N_("Sync with Library"), NULL,
100 	  N_("Synchronize media player with the library"),
101 	  G_CALLBACK (sync_cmd) },
102 };
103 
104 enum
105 {
106 	PROP_0,
107 	PROP_DEVICE_SERIAL,
108 	PROP_ENCODING_TARGET
109 };
110 
111 static GtkActionGroup *action_group = NULL;
112 
113 void
114 rb_media_player_source_init_actions (RBShell *shell)
115 {
116 	GtkUIManager *uimanager;
117 
118 	if (action_group != NULL) {
119 		return;
120 	}
121 
122 	action_group = gtk_action_group_new ("MediaPlayerActions");
123 	gtk_action_group_set_translation_domain (action_group, GETTEXT_PACKAGE);
124 
125 	g_object_get (shell, "ui-manager", &uimanager, NULL);
126 	gtk_ui_manager_insert_action_group (uimanager, action_group, 0);
127 	g_object_unref (uimanager);
128 
129 	_rb_action_group_add_display_page_actions (action_group,
130 						   G_OBJECT (shell),
131 						   rb_media_player_source_actions,
132 						   G_N_ELEMENTS (rb_media_player_source_actions));
133 }
134 
135 static void
136 rb_media_player_source_class_init (RBMediaPlayerSourceClass *klass)
137 {
138 	GObjectClass *object_class = G_OBJECT_CLASS (klass);
139 	RBDisplayPageClass *page_class = RB_DISPLAY_PAGE_CLASS (klass);
140 	RBSourceClass *source_class = RB_SOURCE_CLASS (klass);
141 	RBBrowserSourceClass *browser_source_class = RB_BROWSER_SOURCE_CLASS (klass);
142 
143 	object_class->dispose = rb_media_player_source_dispose;
144 	object_class->set_property = rb_media_player_source_set_property;
145 	object_class->get_property = rb_media_player_source_get_property;
146 	object_class->constructed = rb_media_player_source_constructed;
147 
148 	page_class->receive_drag = impl_receive_drag;
149 	page_class->delete_thyself = impl_delete_thyself;
150 
151 	source_class->impl_can_cut = (RBSourceFeatureFunc) rb_false_function;
152 	source_class->impl_can_copy = (RBSourceFeatureFunc) rb_true_function;
153 	source_class->impl_can_paste = (RBSourceFeatureFunc) rb_false_function;
154 	source_class->impl_can_delete = (RBSourceFeatureFunc) rb_false_function;
155 	source_class->impl_get_delete_action = impl_get_delete_action;
156 	source_class->impl_delete = NULL;
157 
158 	browser_source_class->has_drop_support = (RBBrowserSourceFeatureFunc) rb_false_function;
159 
160 	klass->impl_get_entries = NULL;
161 	klass->impl_get_capacity = NULL;
162 	klass->impl_get_free_space = NULL;
163 	klass->impl_add_playlist = NULL;
164 	klass->impl_remove_playlists = NULL;
165 	klass->impl_show_properties = NULL;
166 
167 	g_object_class_install_property (object_class,
168 					 PROP_DEVICE_SERIAL,
169 					 g_param_spec_string ("serial",
170 							      "serial",
171 							      "device serial number",
172 							      NULL,
173 							      G_PARAM_READABLE));
174 	/**
175 	 * RBMediaPlayerSource:encoding-target:
176 	 *
177 	 * The #GstEncodingTarget for this device
178 	 */
179 	g_object_class_install_property (object_class,
180 					 PROP_ENCODING_TARGET,
181 					 gst_param_spec_mini_object ("encoding-target",
182 								     "encoding target",
183 								     "GstEncodingTarget",
184 								     GST_TYPE_ENCODING_TARGET,
185 								     G_PARAM_READWRITE));
186 
187 	g_type_class_add_private (klass, sizeof (RBMediaPlayerSourcePrivate));
188 }
189 
190 static void
191 rb_media_player_source_dispose (GObject *object)
192 {
193 	RBMediaPlayerSourcePrivate *priv = MEDIA_PLAYER_SOURCE_GET_PRIVATE (object);
194 
195 	if (priv->sync_settings) {
196 		g_object_unref (priv->sync_settings);
197 		priv->sync_settings = NULL;
198 	}
199 
200 	if (priv->sync_state) {
201 		g_object_unref (priv->sync_state);
202 		priv->sync_state = NULL;
203 	}
204 
205 	if (priv->encoding_target) {
206 		gst_encoding_target_unref (priv->encoding_target);
207 		priv->encoding_target = NULL;
208 	}
209 
210 	G_OBJECT_CLASS (rb_media_player_source_parent_class)->dispose (object);
211 }
212 
213 static void
214 rb_media_player_source_init (RBMediaPlayerSource *source)
215 {
216 }
217 
218 static void
219 rb_media_player_source_set_property (GObject *object,
220 			     guint prop_id,
221 			     const GValue *value,
222 			     GParamSpec *pspec)
223 {
224 	RBMediaPlayerSourcePrivate *priv = MEDIA_PLAYER_SOURCE_GET_PRIVATE (object);
225 	switch (prop_id) {
226 	case PROP_ENCODING_TARGET:
227 		if (priv->encoding_target) {
228 			gst_encoding_target_unref (priv->encoding_target);
229 		}
230 		priv->encoding_target = GST_ENCODING_TARGET (gst_value_dup_mini_object (value));
231 		break;
232 	default:
233 		G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
234 		break;
235 	}
236 }
237 
238 static void
239 rb_media_player_source_get_property (GObject *object,
240 			     guint prop_id,
241 			     GValue *value,
242 			     GParamSpec *pspec)
243 {
244 	RBMediaPlayerSourcePrivate *priv = MEDIA_PLAYER_SOURCE_GET_PRIVATE (object);
245 	switch (prop_id) {
246 	case PROP_DEVICE_SERIAL:
247 		/* not actually supported in the base class */
248 		break;
249 	case PROP_ENCODING_TARGET:
250 		gst_value_set_mini_object (value, GST_MINI_OBJECT (priv->encoding_target));
251 		break;
252 	default:
253 		G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
254 		break;
255 	}
256 }
257 
258 static void
259 load_status_changed_cb (GObject *object, GParamSpec *pspec, gpointer whatever)
260 {
261 	RBMediaPlayerSourcePrivate *priv = MEDIA_PLAYER_SOURCE_GET_PRIVATE (object);
262 	RBSourceLoadStatus status;
263 
264 	g_object_get (object, "load-status", &status, NULL);
265 
266 	gtk_action_set_sensitive (priv->sync_action, (status == RB_SOURCE_LOAD_STATUS_LOADED));
267 }
268 
269 static void
270 rb_media_player_source_constructed (GObject *object)
271 {
272 	RBMediaPlayerSourcePrivate *priv = MEDIA_PLAYER_SOURCE_GET_PRIVATE (object);
273 	RBShell *shell;
274 
275 	RB_CHAIN_GOBJECT_METHOD (rb_media_player_source_parent_class, constructed, object);
276 
277 	g_object_get (object, "shell", &shell, NULL);
278 	rb_media_player_source_init_actions (shell);
279 	g_object_unref (shell);
280 
281 	priv->sync_action = gtk_action_group_get_action (action_group, "MediaPlayerSourceSync");
282 	g_signal_connect (object, "notify::load-status", G_CALLBACK (load_status_changed_cb), NULL);
283 	load_status_changed_cb (object, NULL, NULL);
284 }
285 
286 /* must be called once device information is available via source properties */
287 void
288 rb_media_player_source_load		(RBMediaPlayerSource *source)
289 {
290 	RBMediaPlayerSourcePrivate *priv = MEDIA_PLAYER_SOURCE_GET_PRIVATE (source);
291 	char *device_id;
292 	char *sync_filename;
293 	char *sync_path;
294 	char *sync_dir;
295 
296 	/* make sure the sync settings dir exists */
297 	sync_dir = g_build_filename (rb_user_data_dir (), "sync", NULL);
298 	g_mkdir (sync_dir, 0700);
299 
300 	/* construct path to sync settings file */
301 	g_object_get (source, "serial", &device_id, NULL);
302 	if (device_id == NULL) {
303 		g_object_get (source, "name", &device_id, NULL);
304 	}
305 	sync_filename = g_strdup_printf ("device-%s.conf", device_id);
306 	sync_path = g_build_filename (sync_dir, sync_filename, NULL);
307 	g_free (sync_filename);
308 	g_free (device_id);
309 	g_free (sync_dir);
310 
311 	priv->sync_settings = rb_sync_settings_new (sync_path);
312 	g_free (sync_path);
313 }
314 
315 guint64
316 rb_media_player_source_get_capacity (RBMediaPlayerSource *source)
317 {
318 	RBMediaPlayerSourceClass *klass = RB_MEDIA_PLAYER_SOURCE_GET_CLASS (source);
319 
320 	return klass->impl_get_capacity (source);
321 }
322 
323 guint64
324 rb_media_player_source_get_free_space (RBMediaPlayerSource *source)
325 {
326 	RBMediaPlayerSourceClass *klass = RB_MEDIA_PLAYER_SOURCE_GET_CLASS (source);
327 
328 	return klass->impl_get_free_space (source);
329 }
330 
331 /**
332  * rb_media_player_source_get_entries:
333  * @source: the #RBMediaPlayerSource
334  * @category: the sync category name
335  * @entries: (element-type utf8 RB.RhythmDBEntry): map to hold the entries
336  */
337 void
338 rb_media_player_source_get_entries (RBMediaPlayerSource *source,
339 				    const char *category,
340 				    GHashTable *entries)
341 {
342 	RBMediaPlayerSourceClass *klass = RB_MEDIA_PLAYER_SOURCE_GET_CLASS (source);
343 	klass->impl_get_entries (source, category, entries);
344 }
345 
346 /**
347  * rb_media_player_source_delete_entries:
348  * @source: the #RBMediaPlayerSource
349  * @entries: (element-type RB.RhythmDBEntry) (transfer full): list of entries to delete
350  * @callback: callback to call on completion
351  * @user_data: (closure) (scope notified): data for callback
352  * @destroy_data: callback to free the callback data
353  */
354 void
355 rb_media_player_source_delete_entries	(RBMediaPlayerSource *source,
356 					 GList *entries,
357 					 RBMediaPlayerSourceDeleteCallback callback,
358 					 gpointer user_data,
359 					 GDestroyNotify destroy_data)
360 {
361 	RBMediaPlayerSourceClass *klass = RB_MEDIA_PLAYER_SOURCE_GET_CLASS (source);
362 
363 	klass->impl_delete_entries (source, entries, callback, user_data, destroy_data);
364 }
365 
366 static void
367 update_sync (RBMediaPlayerSource *source)
368 {
369 	RBMediaPlayerSourcePrivate *priv = MEDIA_PLAYER_SOURCE_GET_PRIVATE (source);
370 	if (priv->sync_state == NULL) {
371 		priv->sync_state = rb_sync_state_new (source, priv->sync_settings);
372 	} else {
373 		rb_sync_state_update (priv->sync_state);
374 	}
375 }
376 
377 static void
378 properties_dialog_response_cb (GtkDialog *dialog,
379 			       int response_id,
380 			       RBMediaPlayerSource *source)
381 {
382 	RBMediaPlayerSourcePrivate *priv = MEDIA_PLAYER_SOURCE_GET_PRIVATE (source);
383 	rb_debug ("media player properties dialog closed");
384 	gtk_widget_destroy (GTK_WIDGET (dialog));
385 	g_object_unref (priv->properties_dialog);
386 	priv->properties_dialog = NULL;
387 }
388 
389 void
390 rb_media_player_source_show_properties (RBMediaPlayerSource *source)
391 {
392 	RBMediaPlayerSourcePrivate *priv = MEDIA_PLAYER_SOURCE_GET_PRIVATE (source);
393 	RBMediaPlayerSourceClass *klass = RB_MEDIA_PLAYER_SOURCE_GET_CLASS (source);
394 	GtkBuilder *builder;
395 	GtkContainer *container;
396 	const char *ui_file;
397 	char *name;
398 	char *text;
399 
400 	if (priv->properties_dialog != NULL) {
401 		gtk_window_present (GTK_WINDOW (priv->properties_dialog));
402 		return;
403 	}
404 
405 	/* load dialog UI */
406 	ui_file = rb_file ("media-player-properties.ui");
407 	if (ui_file == NULL) {
408 		g_warning ("Couldn't find media-player-properties.ui");
409 		return;
410 	}
411 
412 	builder = rb_builder_load (ui_file, NULL);
413 	if (builder == NULL) {
414 		g_warning ("Couldn't load media-player-properties.ui");
415 		return;
416 	}
417 
418 	priv->properties_dialog = GTK_DIALOG (gtk_builder_get_object (builder, "media-player-properties"));
419 	g_object_ref (priv->properties_dialog);
420 	g_signal_connect_object (priv->properties_dialog,
421 				 "response",
422 				 G_CALLBACK (properties_dialog_response_cb),
423 				 source, 0);
424 
425 	g_object_get (source, "name", &name, NULL);
426 	text = g_strdup_printf (_("%s Properties"), name);
427 	gtk_window_set_title (GTK_WINDOW (priv->properties_dialog), text);
428 	g_free (text);
429 	g_free (name);
430 
431 	/* ensure device usage information is available and up to date */
432 	update_sync (source);
433 
434 	/*
435 	 * fill in some common details:
436 	 * - volume usage (need to hook up signals etc. to update this live)
437 	 */
438 	rb_sync_state_ui_create_bar (&priv->volume_usage, rb_media_player_source_get_capacity (source), NULL);
439 	rb_sync_state_ui_update_volume_usage (&priv->volume_usage, priv->sync_state);
440 
441 	gtk_widget_show_all (priv->volume_usage.widget);
442 	container = GTK_CONTAINER (gtk_builder_get_object (builder, "device-usage-container"));
443 	gtk_container_add (container, priv->volume_usage.widget);
444 
445 
446 	/* let the subclass fill in device type specific details (model names, device names,
447 	 * .. battery levels?) and add more tabs to the notebook to display 'advanced' stuff.
448 	 */
449 
450 	if (klass->impl_show_properties) {
451 		klass->impl_show_properties (source,
452 					     GTK_WIDGET (gtk_builder_get_object (builder, "device-info-box")),
453 					     GTK_WIDGET (gtk_builder_get_object (builder, "media-player-notebook")));
454 	}
455 
456 	/* create sync UI */
457 	container = GTK_CONTAINER (gtk_builder_get_object (builder, "sync-settings-ui-container"));
458 	gtk_scrolled_window_add_with_viewport (GTK_SCROLLED_WINDOW (container), rb_sync_settings_ui_new (source, priv->sync_settings));
459 
460 	container = GTK_CONTAINER (gtk_builder_get_object (builder, "sync-state-ui-container"));
461 	gtk_box_pack_start (GTK_BOX (container), rb_sync_state_ui_new (priv->sync_state), TRUE, TRUE, 0);
462 	gtk_widget_show_all (GTK_WIDGET (container));
463 
464 	gtk_widget_show (GTK_WIDGET (priv->properties_dialog));
465 
466 	g_object_unref (builder);
467 }
468 
469 
470 
471 static void
472 sync_playlists (RBMediaPlayerSource *source)
473 {
474 	RBMediaPlayerSourcePrivate *priv = MEDIA_PLAYER_SOURCE_GET_PRIVATE (source);
475 	RBMediaPlayerSourceClass *klass = RB_MEDIA_PLAYER_SOURCE_GET_CLASS (source);
476 	RBPlaylistManager *playlist_manager;
477 	RBShell *shell;
478 	GHashTable *device;
479 	GList *all_playlists;
480 	GList *l;
481 
482 	if (klass->impl_add_playlist == NULL || klass->impl_remove_playlists == NULL) {
483 		rb_debug ("source class doesn't support playlists");
484 		return;
485 	}
486 
487 	/* build an updated device contents map, so we can find the device entries
488 	 * corresponding to the entries in the local playlists.
489 	 */
490 	device = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, (GDestroyNotify)rhythmdb_entry_unref);
491 	rb_media_player_source_get_entries (source, SYNC_CATEGORY_MUSIC, device);
492 
493 	/* remove all playlists from the device, then add the synced playlists. */
494 	klass->impl_remove_playlists (source);
495 
496 	/* get all local playlists */
497 	g_object_get (source, "shell", &shell, NULL);
498 	g_object_get (shell, "playlist-manager", &playlist_manager, NULL);
499 	all_playlists = rb_playlist_manager_get_playlists (playlist_manager);
500 	g_object_unref (playlist_manager);
501 	g_object_unref (shell);
502 
503 	for (l = all_playlists; l != NULL; l = l->next) {
504 		char *name;
505 		RBSource *playlist_source = RB_SOURCE (l->data);
506 		RhythmDBQueryModel *model;
507 		GList *tracks = NULL;
508 		GtkTreeIter iter;
509 
510 		/* is this playlist selected for syncing? */
511 		g_object_get (playlist_source, "name", &name, NULL);
512 		if (rb_sync_settings_group_enabled (priv->sync_settings, SYNC_CATEGORY_MUSIC, name) == FALSE) {
513 			rb_debug ("not syncing playlist %s", name);
514 			g_free (name);
515 			continue;
516 		}
517 
518 		/* match playlist entries to entries on the device */
519 		g_object_get (playlist_source, "base-query-model", &model, NULL);
520 		if (gtk_tree_model_get_iter_first (GTK_TREE_MODEL (model), &iter) == FALSE) {
521 			rb_debug ("not syncing empty playlist %s", name);
522 			g_free (name);
523 			g_object_unref (model);
524 			continue;
525 		}
526 
527 		do {
528 			char *trackid;
529 			RhythmDBEntry *entry;
530 			RhythmDBEntry *device_entry;
531 
532 			entry = rhythmdb_query_model_iter_to_entry (model, &iter);
533 			trackid = rb_sync_state_make_track_uuid (entry);
534 
535 			device_entry = g_hash_table_lookup (device, trackid);
536 			if (device_entry != NULL) {
537 				tracks = g_list_prepend (tracks, device_entry);
538 			} else {
539 				rb_debug ("unable to find entry on device for track %s (id %s)",
540 					  rhythmdb_entry_get_string (entry, RHYTHMDB_PROP_LOCATION),
541 					  trackid);
542 			}
543 			g_free (trackid);
544 
545 		} while (gtk_tree_model_iter_next (GTK_TREE_MODEL (model), &iter));
546 
547 		tracks = g_list_reverse (tracks);
548 
549 		/* transfer the playlist to the device */
550 		rb_debug ("syncing playlist %s", name);
551 		klass->impl_add_playlist (source, name, tracks);
552 
553 		g_free (name);
554 		g_list_free (tracks);
555 		g_object_unref (model);
556 	}
557 
558 	g_hash_table_destroy (device);
559 }
560 
561 static gboolean
562 sync_idle_cb_cleanup (RBMediaPlayerSource *source)
563 {
564 	RBMediaPlayerSourcePrivate *priv = MEDIA_PLAYER_SOURCE_GET_PRIVATE (source);
565 
566 	rb_debug ("cleaning up after sync process");
567 
568 	gtk_action_set_sensitive (priv->sync_action, TRUE);
569 
570 	return FALSE;
571 }
572 
573 static gboolean
574 sync_idle_cb_playlists (RBMediaPlayerSource *source)
575 {
576 	/* Transfer the playlists */
577 	rb_debug ("transferring playlists to the device");
578 	sync_playlists (source);
579 	g_idle_add ((GSourceFunc)sync_idle_cb_cleanup, source);
580 	return FALSE;
581 }
582 
583 static void
584 transfer_batch_complete_cb (RBTrackTransferBatch *batch, RBMediaPlayerSource *source)
585 {
586 	rb_debug ("finished transferring files to the device");
587 	g_idle_add ((GSourceFunc) sync_idle_cb_playlists, source);
588 }
589 
590 static void
591 transfer_batch_cancelled_cb (RBTrackTransferBatch *batch, RBMediaPlayerSource *source)
592 {
593 	/* don't try to update playlists, just clean up */
594 	rb_debug ("sync file transfer to the device was cancelled");
595 	g_idle_add ((GSourceFunc) sync_idle_cb_cleanup, source);
596 }
597 
598 
599 static void
600 sync_delete_done_cb (RBMediaPlayerSource *source, gpointer dontcare)
601 {
602 	RBMediaPlayerSourcePrivate *priv = MEDIA_PLAYER_SOURCE_GET_PRIVATE (source);
603 	rb_debug ("finished deleting %d files from media player", priv->sync_state->sync_remove_count);
604 
605 	/* Transfer needed tracks and podcasts from itinerary to device */
606 	if (priv->sync_state->sync_add_count != 0) {
607 		RBTrackTransferBatch *batch;
608 
609 		rb_debug ("transferring %d files to media player", priv->sync_state->sync_add_count);
610 		batch = rb_source_paste (RB_SOURCE (source), priv->sync_state->sync_to_add);
611 		if (batch != NULL) {
612 			g_signal_connect_object (batch, "complete", G_CALLBACK (transfer_batch_complete_cb), source, 0);
613 			g_signal_connect_object (batch, "cancelled", G_CALLBACK (transfer_batch_cancelled_cb), source, 0);
614 		} else {
615 			rb_debug ("weird, transfer was apparently synchronous");
616 			g_idle_add ((GSourceFunc) sync_idle_cb_playlists, source);
617 		}
618 	} else {
619 		rb_debug ("no files to transfer to the device");
620 		g_idle_add ((GSourceFunc) sync_idle_cb_playlists, source);
621 	}
622 }
623 
624 static gboolean
625 sync_has_items_enabled (RBMediaPlayerSource *source)
626 {
627 	RBMediaPlayerSourcePrivate *priv = MEDIA_PLAYER_SOURCE_GET_PRIVATE (source);
628 	if (rb_sync_settings_sync_category (priv->sync_settings, SYNC_CATEGORY_MUSIC) == FALSE &&
629 	    rb_sync_settings_has_enabled_groups (priv->sync_settings, SYNC_CATEGORY_MUSIC) == FALSE &&
630 	    rb_sync_settings_sync_category (priv->sync_settings, SYNC_CATEGORY_PODCAST) == FALSE &&
631 	    rb_sync_settings_has_enabled_groups (priv->sync_settings, SYNC_CATEGORY_PODCAST) == FALSE) {
632 		rb_debug ("no sync items are enabled");
633 		return FALSE;
634 	}
635 
636 	return TRUE;
637 }
638 
639 static gboolean
640 sync_has_enough_space (RBMediaPlayerSource *source)
641 {
642 	RBMediaPlayerSourcePrivate *priv = MEDIA_PLAYER_SOURCE_GET_PRIVATE (source);
643 	if (priv->sync_state->sync_space_needed > rb_media_player_source_get_capacity (source)) {
644 		rb_debug ("not enough space for selected sync items");
645 		return FALSE;
646 	}
647 
648 	return TRUE;
649 }
650 
651 static void
652 update_sync_settings_dialog (RBMediaPlayerSource *source)
653 {
654 	RBMediaPlayerSourcePrivate *priv = MEDIA_PLAYER_SOURCE_GET_PRIVATE (source);
655 	gboolean can_continue;
656 
657 	if (sync_has_items_enabled (source) == FALSE) {
658 		can_continue = FALSE;
659 		gtk_label_set_text (GTK_LABEL (priv->sync_dialog_label),
660 				    _("You have not selected any music, playlists, or podcasts to transfer to this device."));
661 	} else if (sync_has_enough_space (source) == FALSE) {
662 		can_continue = FALSE;
663 		gtk_label_set_text (GTK_LABEL (priv->sync_dialog_label),
664 				    _("There is not enough space on the device to transfer the selected music, playlists and podcasts."));
665 	} else {
666 		can_continue = TRUE;
667 	}
668 
669 	gtk_widget_set_visible (priv->sync_dialog_error_box, !can_continue);
670 	gtk_dialog_set_response_sensitive (GTK_DIALOG (priv->sync_dialog), GTK_RESPONSE_YES, can_continue);
671 }
672 
673 static void
674 sync_dialog_state_update (RBSyncState *state, RBMediaPlayerSource *source)
675 {
676 	update_sync_settings_dialog (source);
677 }
678 
679 static void
680 sync_confirm_dialog_cb (GtkDialog *dialog,
681 			gint response_id,
682 			RBMediaPlayerSource *source)
683 {
684 	RBMediaPlayerSourcePrivate *priv = MEDIA_PLAYER_SOURCE_GET_PRIVATE (source);
685 
686 	g_signal_handler_disconnect (priv->sync_state, priv->sync_dialog_update_id);
687 	priv->sync_dialog_update_id = 0;
688 
689 	gtk_widget_destroy (GTK_WIDGET (dialog));
690 	priv->sync_dialog = NULL;
691 	priv->sync_dialog_label = NULL;
692 
693 	if (response_id != GTK_RESPONSE_YES) {
694 		rb_debug ("user doesn't want to sync");
695 		g_idle_add ((GSourceFunc)sync_idle_cb_cleanup, source);
696 	} else {
697 		rb_debug ("user wants to sync");
698 		g_idle_add ((GSourceFunc) sync_idle_delete_entries, source);
699 	}
700 }
701 
702 
703 static void
704 display_sync_settings_dialog (RBMediaPlayerSource *source)
705 {
706 	RBMediaPlayerSourcePrivate *priv = MEDIA_PLAYER_SOURCE_GET_PRIVATE (source);
707 	GtkWidget *content;
708 	GtkWidget *widget;
709 	GtkBuilder *builder;
710 	const char *ui_file;
711 	char *name;
712 	char *title;
713 
714 	g_object_get (source, "name", &name, NULL);
715 	title = g_strdup_printf (_("%s Sync Settings"), name);
716 
717 	priv->sync_dialog = gtk_dialog_new_with_buttons (title,
718 							 NULL,
719 							 0,
720 							 _("Sync with the device"),
721 							 GTK_RESPONSE_YES,
722 							 _("Don't sync"),
723 							 GTK_RESPONSE_CANCEL,
724 							 NULL);
725 	g_free (title);
726 
727 	priv->sync_dialog_update_id = g_signal_connect_object (priv->sync_state,
728 							       "updated",
729 							       G_CALLBACK (sync_dialog_state_update),
730 							       source, 0);
731 	g_signal_connect_object (priv->sync_dialog,
732 				 "response",
733 				 G_CALLBACK (sync_confirm_dialog_cb),
734 				 source, 0);
735 
736 	/* display the sync settings, the sync state, and some helpful text indicating why
737 	 * we're not syncing already
738 	 */
739 	content = gtk_dialog_get_content_area (GTK_DIALOG (priv->sync_dialog));
740 
741 	ui_file = rb_file ("sync-dialog.ui");
742 	if (ui_file == NULL) {
743 		g_warning ("Couldn't find sync-state.ui");
744 		gtk_widget_show_all (priv->sync_dialog);
745 		return;
746 	}
747 
748 	builder = rb_builder_load (ui_file, NULL);
749 	if (builder == NULL) {
750 		g_warning ("Couldn't load sync-state.ui");
751 		gtk_widget_show_all (priv->sync_dialog);
752 		return;
753 	}
754 
755 	priv->sync_dialog_label = GTK_WIDGET (gtk_builder_get_object (builder, "sync-dialog-reason"));
756 	priv->sync_dialog_error_box = GTK_WIDGET (gtk_builder_get_object (builder, "sync-dialog-message"));
757 
758 	widget = GTK_WIDGET (gtk_builder_get_object (builder, "sync-settings-ui-container"));
759 	gtk_scrolled_window_add_with_viewport (GTK_SCROLLED_WINDOW (widget), rb_sync_settings_ui_new (source, priv->sync_settings));
760 
761 	widget = GTK_WIDGET (gtk_builder_get_object (builder, "sync-state-ui-container"));
762 	gtk_box_pack_start (GTK_BOX (widget), rb_sync_state_ui_new (priv->sync_state), TRUE, TRUE, 0);
763 
764 	widget = GTK_WIDGET (gtk_builder_get_object (builder, "sync-dialog"));
765 	gtk_box_pack_start (GTK_BOX (content), widget, TRUE, TRUE, 0);
766 
767 	gtk_widget_show_all (priv->sync_dialog);
768 	update_sync_settings_dialog (source);
769 	g_object_unref (builder);
770 }
771 
772 static gboolean
773 sync_idle_cb_update_sync (RBMediaPlayerSource *source)
774 {
775 	update_sync (source);
776 
777 	if (sync_has_items_enabled (source) == FALSE || sync_has_enough_space (source) == FALSE) {
778 		rb_debug ("displaying sync settings dialog");
779 		display_sync_settings_dialog (source);
780 		return FALSE;
781 	}
782 
783 	rb_debug ("sync settings are acceptable");
784 	return sync_idle_delete_entries (source);
785 }
786 
787 static gboolean
788 sync_idle_delete_entries (RBMediaPlayerSource *source)
789 {
790 	RBMediaPlayerSourcePrivate *priv = MEDIA_PLAYER_SOURCE_GET_PRIVATE (source);
791 	rb_debug ("deleting %d files from media player", priv->sync_state->sync_remove_count);
792 	rb_media_player_source_delete_entries (source,
793 					       priv->sync_state->sync_to_remove,
794 					       (RBMediaPlayerSourceDeleteCallback) sync_delete_done_cb,
795 					       NULL,
796 					       NULL);
797 	return FALSE;
798 }
799 
800 void
801 rb_media_player_source_sync (RBMediaPlayerSource *source)
802 {
803 	RBMediaPlayerSourcePrivate *priv = MEDIA_PLAYER_SOURCE_GET_PRIVATE (source);
804 
805 	gtk_action_set_sensitive (priv->sync_action, FALSE);
806 
807 	g_idle_add ((GSourceFunc)sync_idle_cb_update_sync, source);
808 }
809 
810 void
811 _rb_media_player_source_add_to_map (GHashTable *map, RhythmDBEntry *entry)
812 {
813 	g_hash_table_insert (map,
814 			     rb_sync_state_make_track_uuid (entry),
815 			     rhythmdb_entry_ref (entry));
816 }
817 
818 static void
819 sync_cmd (GtkAction *action, RBSource *source)
820 {
821 	rb_media_player_source_sync (RB_MEDIA_PLAYER_SOURCE (source));
822 }
823 
824 static RhythmDB *
825 get_db_for_source (RBSource *source)
826 {
827 	RBShell *shell;
828 	RhythmDB *db;
829 
830 	g_object_get (source, "shell", &shell, NULL);
831 	g_object_get (shell, "db", &db, NULL);
832 	g_object_unref (shell);
833 
834 	return db;
835 }
836 
837 gboolean
838 impl_receive_drag (RBDisplayPage *page, GtkSelectionData *data)
839 {
840 	GList *entries;
841 	RhythmDB *db;
842 	char *type;
843 
844 	entries = NULL;
845 	type = gdk_atom_name (gtk_selection_data_get_data_type (data));
846         db = get_db_for_source (RB_SOURCE (page));
847 
848 	if (strcmp (type, "text/uri-list") == 0) {
849 		GList *list;
850 		GList *i;
851 
852 		rb_debug ("parsing uri list");
853 		list = rb_uri_list_parse ((const char *) gtk_selection_data_get_data (data));
854 
855 		for (i = list; i != NULL; i = g_list_next (i)) {
856 			char *uri;
857 			RhythmDBEntry *entry;
858 
859 			if (i->data == NULL)
860 				continue;
861 
862 			uri = i->data;
863 			entry = rhythmdb_entry_lookup_by_location (db, uri);
864 
865 			if (entry == NULL) {
866 				/* add to the library */
867 				rb_debug ("received drop of unknown uri: %s", uri);
868 			} else {
869 				/* add to list of entries to copy */
870 				entries = g_list_prepend (entries, entry);
871 			}
872 			g_free (uri);
873 		}
874 		g_list_free (list);
875 	} else if (strcmp (type, "application/x-rhythmbox-entry") == 0) {
876 		char **list;
877 		char **i;
878 
879 		rb_debug ("parsing entry ids");
880 		list = g_strsplit ((const char*) gtk_selection_data_get_data (data), "\n", -1);
881 		for (i = list; *i != NULL; i++) {
882 			RhythmDBEntry *entry;
883 			gulong id;
884 
885 			id = atoi (*i);
886 			entry = rhythmdb_entry_lookup_by_id (db, id);
887 			if (entry != NULL)
888 				entries = g_list_prepend (entries, entry);
889 		}
890 
891 		g_strfreev (list);
892 	} else {
893 		rb_debug ("received unknown drop type");
894 	}
895 
896 	g_object_unref (db);
897 	g_free (type);
898 
899 	if (entries) {
900 		entries = g_list_reverse (entries);
901 		if (rb_source_can_paste (RB_SOURCE (page))) {
902 			rb_source_paste (RB_SOURCE (page), entries);
903 		}
904 		g_list_free (entries);
905 	}
906 
907 	return TRUE;
908 }
909 
910 static char *
911 impl_get_delete_action (RBSource *source)
912 {
913 	return g_strdup ("EditDelete");
914 }
915 
916 static void
917 impl_delete_thyself (RBDisplayPage *page)
918 {
919 	RhythmDB *db;
920 	RBShell *shell;
921 	RhythmDBEntryType *entry_type;
922 
923 	g_object_get (page, "shell", &shell, NULL);
924 	g_object_get (shell, "db", &db, NULL);
925 	g_object_unref (shell);
926 
927 	g_object_get (page, "entry-type", &entry_type, NULL);
928 	rb_debug ("deleting all entries of type '%s'", rhythmdb_entry_type_get_name (entry_type));
929 	rhythmdb_entry_delete_by_type (db, entry_type);
930 	g_object_unref (entry_type);
931 
932 	rhythmdb_commit (db);
933 	g_object_unref (db);
934 }
935 
936 /* annotations for methods */
937 
938 /**
939  * impl_delete_entries:
940  * @source: the source
941  * @entries: (element-type RB.RhythmDBEntry) (transfer full): list of entries to delete
942  * @callback: callback to call on completion
943  * @data: (closure) (scope notified): callback data
944  * @destroy_data: callback to free callback data
945  */
946 
947 /**
948  * impl_add_playlist:
949  * @source: the source
950  * @name: new playlist name
951  * @entries: (element-type RB.RhythmDBEntry) (transfer full): list of entries to add
952  */