No issues found
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 |
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 */