Merge lp:~matttbe/ubuntu/quantal/rhythmbox/1010619_795765 into lp:ubuntu/quantal/rhythmbox

Proposed by Matthieu Baerts
Status: Merged
Merge reported by: Martin Pitt
Merged at revision: not available
Proposed branch: lp:~matttbe/ubuntu/quantal/rhythmbox/1010619_795765
Merge into: lp:ubuntu/quantal/rhythmbox
Diff against target: 9647 lines (+9557/-4)
9 files modified
.pc/11_fix_cd_pausing.patch/shell/rb-shell-player.c (+3978/-0)
.pc/applied-patches (+2/-0)
.pc/git_rhythmdb_fix_deadlock.patch/rhythmdb/rhythmdb.c (+5478/-0)
debian/changelog (+16/-0)
debian/patches/11_fix_cd_pausing.patch (+23/-0)
debian/patches/git_rhythmdb_fix_deadlock.patch (+46/-0)
debian/patches/series (+2/-0)
rhythmdb/rhythmdb.c (+8/-4)
shell/rb-shell-player.c (+4/-0)
To merge this branch: bzr merge lp:~matttbe/ubuntu/quantal/rhythmbox/1010619_795765
Reviewer Review Type Date Requested Status
Ubuntu Sponsors Pending
Review via email: mp+133343@code.launchpad.net

Description of the change

[IMPACT]

 * Rhythmbox freezes when using a large collection. (LP: #1010619)
 * Rhythmbox won't resume playing an audio CD if you pause it. (LP: #795765)

[TESTCASE]

 * Rhythmbox freezes when using a large collection. (LP: #1010619)
  * It happens with a large collection.
  * It's due to a deadlock between db load and rhythmdb_add_uri_with_types and it's possible that it's not easy to reproduce this bug. Maybe it can be interesting to slow down the hard disk (e.g. by copying a large file when opening Rhythmbox with a lot of files in its collection)
  * With the previous version and after a few seconds, Rhythmbox will freeze.
  * (this fix has been confirmed by a few people in the bug #1010619)

 * Rhythmbox won't resume playing an audio CD if you pause it. (LP: #795765)
  * Insert an audio CD (not an mp3 CD but a traditional one you'd buy from the music store)
  * Test if play and pause work.

[Regression Potential]

 * Rhythmbox freezes when using a large collection. (LP: #1010619)
  * According to RB devs: None => http://git.gnome.org/browse/rhythmbox/commit/?id=a2ead52faa45a37f28dff1e94cad5f0dadd28129
  * This deadlock has been fixed by RB devs and now it seems they understand why there was a deadlock

 * Rhythmbox won't resume playing an audio CD if you pause it. (LP: #795765)
  * None, audio CD playback is already very badly broken.

[ Other Info ]

 * A new version (2.98-0ubuntu2) has been uploaded to Raring repos and it contains the same patches to fix these bugs.
 * About the version: I wanted to use the version 2.97-1ubuntu5.1 just to be sure that the version number does not conflict with any later and future versions in other Ubuntu releases but a new version has been uploaded a few days ago with the version 2.97-1ubuntu6. I hope I can use the version 2.97-1ubuntu7

Thank you for your help!

To post a comment you must log in.
Revision history for this message
Sebastien Bacher (seb128) wrote :

Thank you for your work, the update has been uploaded and is waiting for SRU team review in the queue

Revision history for this message
Matthieu Baerts (matttbe) wrote :

Thank you for your help :)

Revision history for this message
Mathieu Cossette (mcossette) wrote :

critical bug here.. when reaching end of a song which causes to crash Rhythmbox

Ubuntu 13.10

Revision history for this message
Matthieu Baerts (matttbe) wrote :

@Mathieu: I guess it's not linked to this bug but I see that you also opened a new bug report (LP: #1179100). Let wait and see if other people have this critical bug.

PS: feel free to also report this bug upstream on bugzilla.gnome.org

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== added directory '.pc/11_fix_cd_pausing.patch'
2=== added directory '.pc/11_fix_cd_pausing.patch/shell'
3=== added file '.pc/11_fix_cd_pausing.patch/shell/rb-shell-player.c'
4--- .pc/11_fix_cd_pausing.patch/shell/rb-shell-player.c 1970-01-01 00:00:00 +0000
5+++ .pc/11_fix_cd_pausing.patch/shell/rb-shell-player.c 2012-11-07 20:44:28 +0000
6@@ -0,0 +1,3978 @@
7+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*-
8+ *
9+ * Copyright (C) 2002, 2003 Jorn Baayen <jorn@nl.linux.org>
10+ * Copyright (C) 2002,2003 Colin Walters <walters@debian.org>
11+ *
12+ * This program is free software; you can redistribute it and/or modify
13+ * it under the terms of the GNU General Public License as published by
14+ * the Free Software Foundation; either version 2 of the License, or
15+ * (at your option) any later version.
16+ *
17+ * The Rhythmbox authors hereby grant permission for non-GPL compatible
18+ * GStreamer plugins to be used and distributed together with GStreamer
19+ * and Rhythmbox. This permission is above and beyond the permissions granted
20+ * by the GPL license by which Rhythmbox is covered. If you modify this code
21+ * you may extend this exception to your version of the code, but you are not
22+ * obligated to do so. If you do not wish to do so, delete this exception
23+ * statement from your version.
24+ *
25+ * This program is distributed in the hope that it will be useful,
26+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
27+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
28+ * GNU General Public License for more details.
29+ *
30+ * You should have received a copy of the GNU General Public License
31+ * along with this program; if not, write to the Free Software
32+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
33+ *
34+ */
35+
36+/**
37+ * SECTION:rb-shell-player
38+ * @short_description: playback state management
39+ *
40+ * The shell player (or player shell, depending on who you're talking to)
41+ * manages the #RBPlayer instance, tracks the current playing #RhythmDBEntry,
42+ * and manages the various #RBPlayOrder instances. It provides simple operations
43+ * such as next, previous, play/pause, and seek.
44+ *
45+ * When playing internet radio streams, it first attempts to read the stream URL
46+ * as a playlist. If this succeeds, the URLs from the playlist are stored in a
47+ * list and tried in turn in case of errors. If the playlist parsing fails, the
48+ * stream URL is played directly.
49+ *
50+ * The mapping from the separate shuffle and repeat settings to an #RBPlayOrder
51+ * instance occurs in here. The play order logic can also support a number of
52+ * additional play orders not accessible via the shuffle and repeat buttons.
53+ *
54+ * If the player backend supports multiple streams, the shell player crossfades
55+ * between streams by watching the elapsed time of the current stream and simulating
56+ * an end-of-stream event when it gets within the crossfade duration of the actual
57+ * end.
58+ */
59+
60+#include "config.h"
61+
62+#include <unistd.h>
63+#include <stdlib.h>
64+#include <time.h>
65+#include <string.h>
66+
67+#include <glib.h>
68+#include <glib/gi18n.h>
69+#include <gtk/gtk.h>
70+
71+#include "rb-property-view.h"
72+#include "rb-shell-player.h"
73+#include "rb-stock-icons.h"
74+#include "rb-builder-helpers.h"
75+#include "rb-file-helpers.h"
76+#include "rb-cut-and-paste-code.h"
77+#include "rb-dialog.h"
78+#include "rb-debug.h"
79+#include "rb-player.h"
80+#include "rb-header.h"
81+#include "totem-pl-parser.h"
82+#include "rb-metadata.h"
83+#include "rb-library-source.h"
84+#include "rb-util.h"
85+#include "rb-play-order.h"
86+#include "rb-playlist-source.h"
87+#include "rb-play-queue-source.h"
88+#include "rhythmdb.h"
89+#include "rb-podcast-manager.h"
90+#include "rb-marshal.h"
91+#include "rb-missing-plugins.h"
92+#include "rb-ext-db.h"
93+
94+/* Play Orders */
95+#include "rb-play-order-linear.h"
96+#include "rb-play-order-linear-loop.h"
97+#include "rb-play-order-shuffle.h"
98+#include "rb-play-order-random-equal-weights.h"
99+#include "rb-play-order-random-by-age.h"
100+#include "rb-play-order-random-by-rating.h"
101+#include "rb-play-order-random-by-age-and-rating.h"
102+#include "rb-play-order-queue.h"
103+
104+static const char* const state_to_play_order[2][2] =
105+ {{"linear", "linear-loop"},
106+ {"shuffle", "random-by-age-and-rating"}};
107+
108+static void rb_shell_player_class_init (RBShellPlayerClass *klass);
109+static void rb_shell_player_init (RBShellPlayer *shell_player);
110+static void rb_shell_player_constructed (GObject *object);
111+static void rb_shell_player_dispose (GObject *object);
112+static void rb_shell_player_finalize (GObject *object);
113+static void rb_shell_player_set_property (GObject *object,
114+ guint prop_id,
115+ const GValue *value,
116+ GParamSpec *pspec);
117+static void rb_shell_player_get_property (GObject *object,
118+ guint prop_id,
119+ GValue *value,
120+ GParamSpec *pspec);
121+
122+static void rb_shell_player_cmd_previous (GtkAction *action,
123+ RBShellPlayer *player);
124+static void rb_shell_player_cmd_play (GtkAction *action,
125+ RBShellPlayer *player);
126+static void rb_shell_player_cmd_next (GtkAction *action,
127+ RBShellPlayer *player);
128+static void rb_shell_player_cmd_volume_up (GtkAction *action,
129+ RBShellPlayer *player);
130+static void rb_shell_player_cmd_volume_down (GtkAction *action,
131+ RBShellPlayer *player);
132+static void rb_shell_player_shuffle_changed_cb (GtkAction *action,
133+ RBShellPlayer *player);
134+static void rb_shell_player_repeat_changed_cb (GtkAction *action,
135+ RBShellPlayer *player);
136+static void rb_shell_player_set_playing_source_internal (RBShellPlayer *player,
137+ RBSource *source,
138+ gboolean sync_entry_view);
139+static void rb_shell_player_sync_with_source (RBShellPlayer *player);
140+static void rb_shell_player_sync_with_selected_source (RBShellPlayer *player);
141+static void rb_shell_player_entry_changed_cb (RhythmDB *db,
142+ RhythmDBEntry *entry,
143+ GValueArray *changes,
144+ RBShellPlayer *player);
145+
146+static void rb_shell_player_entry_activated_cb (RBEntryView *view,
147+ RhythmDBEntry *entry,
148+ RBShellPlayer *player);
149+static void rb_shell_player_property_row_activated_cb (RBPropertyView *view,
150+ const char *name,
151+ RBShellPlayer *player);
152+static void rb_shell_player_sync_volume (RBShellPlayer *player, gboolean notify, gboolean set_volume);
153+static void tick_cb (RBPlayer *player, RhythmDBEntry *entry, gint64 elapsed, gint64 duration, gpointer data);
154+static void error_cb (RBPlayer *player, RhythmDBEntry *entry, const GError *err, gpointer data);
155+static void missing_plugins_cb (RBPlayer *player, RhythmDBEntry *entry, const char **details, const char **descriptions, RBShellPlayer *sp);
156+static void playing_stream_cb (RBPlayer *player, RhythmDBEntry *entry, RBShellPlayer *shell_player);
157+static void player_image_cb (RBPlayer *player, RhythmDBEntry *entry, GdkPixbuf *image, RBShellPlayer *shell_player);
158+static void rb_shell_player_error (RBShellPlayer *player, gboolean async, const GError *err);
159+
160+static void rb_shell_player_play_order_update_cb (RBPlayOrder *porder,
161+ gboolean has_next,
162+ gboolean has_previous,
163+ RBShellPlayer *player);
164+
165+static void rb_shell_player_sync_play_order (RBShellPlayer *player);
166+static void rb_shell_player_sync_control_state (RBShellPlayer *player);
167+static void rb_shell_player_sync_buttons (RBShellPlayer *player);
168+
169+static void player_settings_changed_cb (GSettings *settings,
170+ const char *key,
171+ RBShellPlayer *player);
172+static void rb_shell_player_playing_changed_cb (RBShellPlayer *player,
173+ GParamSpec *arg1,
174+ gpointer user_data);
175+static void rb_shell_player_extra_metadata_cb (RhythmDB *db,
176+ RhythmDBEntry *entry,
177+ const char *field,
178+ GValue *metadata,
179+ RBShellPlayer *player);
180+
181+static gboolean rb_shell_player_open_location (RBShellPlayer *player,
182+ RhythmDBEntry *entry,
183+ RBPlayerPlayType play_type,
184+ GError **error);
185+static gboolean rb_shell_player_do_next_internal (RBShellPlayer *player,
186+ gboolean from_eos,
187+ gboolean allow_stop,
188+ GError **error);
189+static void rb_shell_player_slider_dragging_cb (GObject *header,
190+ GParamSpec *pspec,
191+ RBShellPlayer *player);
192+static void rb_shell_player_volume_changed_cb (RBPlayer *player,
193+ float volume,
194+ RBShellPlayer *shell_player);
195+
196+
197+
198+typedef struct {
199+ /** Value of the state/play-order setting */
200+ char *name;
201+ /** Contents of the play order dropdown; should be gettext()ed before use. */
202+ char *description;
203+ /** the play order's gtype id */
204+ GType order_type;
205+ /** TRUE if the play order should appear in the dropdown */
206+ gboolean is_in_dropdown;
207+} RBPlayOrderDescription;
208+
209+static void _play_order_description_free (RBPlayOrderDescription *order);
210+
211+static RBPlayOrder* rb_play_order_new (RBShellPlayer *player, const char* porder_name);
212+
213+/* number of nanoseconds before the end of a track to start prerolling the next */
214+#define PREROLL_TIME RB_PLAYER_SECOND
215+
216+struct RBShellPlayerPrivate
217+{
218+ RhythmDB *db;
219+
220+ gboolean syncing_state;
221+ gboolean queue_only;
222+
223+ RBSource *selected_source;
224+ RBSource *source;
225+ RBPlayQueueSource *queue_source;
226+ RBSource *current_playing_source;
227+
228+ GHashTable *play_orders; /* char* -> RBPlayOrderDescription* map */
229+
230+ gboolean did_retry;
231+ GTimeVal last_retry;
232+
233+ GtkUIManager *ui_manager;
234+ GtkActionGroup *actiongroup;
235+
236+ gboolean handling_error;
237+
238+ RBPlayer *mmplayer;
239+
240+ guint elapsed;
241+ gint64 track_transition_time;
242+ RhythmDBEntry *playing_entry;
243+ gboolean playing_entry_eos;
244+ gboolean jump_to_playing_entry;
245+
246+ RBPlayOrder *play_order;
247+ RBPlayOrder *queue_play_order;
248+
249+ GQueue *playlist_urls;
250+ GCancellable *parser_cancellable;
251+
252+ RBHeader *header_widget;
253+
254+ GSettings *settings;
255+ GSettings *ui_settings;
256+
257+ gboolean has_prev;
258+ gboolean has_next;
259+ gboolean mute;
260+ float volume;
261+
262+ guint do_next_idle_id;
263+ guint unblock_play_id;
264+};
265+
266+#define RB_SHELL_PLAYER_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), RB_TYPE_SHELL_PLAYER, RBShellPlayerPrivate))
267+
268+enum
269+{
270+ PROP_0,
271+ PROP_SOURCE,
272+ PROP_DB,
273+ PROP_UI_MANAGER,
274+ PROP_ACTION_GROUP,
275+ PROP_PLAY_ORDER,
276+ PROP_PLAYING,
277+ PROP_VOLUME,
278+ PROP_HEADER,
279+ PROP_QUEUE_SOURCE,
280+ PROP_QUEUE_ONLY,
281+ PROP_PLAYING_FROM_QUEUE,
282+ PROP_PLAYER,
283+ PROP_MUTE,
284+ PROP_HAS_NEXT,
285+ PROP_HAS_PREV
286+};
287+
288+enum
289+{
290+ WINDOW_TITLE_CHANGED,
291+ ELAPSED_CHANGED,
292+ PLAYING_SOURCE_CHANGED,
293+ PLAYING_CHANGED,
294+ PLAYING_SONG_CHANGED,
295+ PLAYING_URI_CHANGED,
296+ PLAYING_SONG_PROPERTY_CHANGED,
297+ ELAPSED_NANO_CHANGED,
298+ LAST_SIGNAL
299+};
300+
301+static GtkActionEntry rb_shell_player_actions [] =
302+{
303+ { "ControlPlay", GTK_STOCK_MEDIA_PLAY, N_("_Play"), "<control>space",
304+ N_("Start playback"),
305+ G_CALLBACK (rb_shell_player_cmd_play) },
306+ { "ControlPrevious", GTK_STOCK_MEDIA_PREVIOUS, N_("Pre_vious"), "<alt>Left",
307+ N_("Start playing the previous song"),
308+ G_CALLBACK (rb_shell_player_cmd_previous) },
309+ { "ControlNext", GTK_STOCK_MEDIA_NEXT, N_("_Next"), "<alt>Right",
310+ N_("Start playing the next song"),
311+ G_CALLBACK (rb_shell_player_cmd_next) },
312+ { "ControlVolumeUp", NULL, N_("_Increase Volume"), "<control>Up",
313+ N_("Increase playback volume"),
314+ G_CALLBACK (rb_shell_player_cmd_volume_up) },
315+ { "ControlVolumeDown", NULL, N_("_Decrease Volume"), "<control>Down",
316+ N_("Decrease playback volume"),
317+ G_CALLBACK (rb_shell_player_cmd_volume_down) },
318+};
319+static guint rb_shell_player_n_actions = G_N_ELEMENTS (rb_shell_player_actions);
320+
321+static GtkToggleActionEntry rb_shell_player_toggle_entries [] =
322+{
323+ { "ControlShuffle", GNOME_MEDIA_SHUFFLE, N_("Sh_uffle"), "<control>U",
324+ N_("Play songs in a random order"),
325+ G_CALLBACK (rb_shell_player_shuffle_changed_cb) },
326+ { "ControlRepeat", GNOME_MEDIA_REPEAT, N_("_Repeat"), "<control>R",
327+ N_("Play first song again after all songs are played"),
328+ G_CALLBACK (rb_shell_player_repeat_changed_cb) },
329+};
330+static guint rb_shell_player_n_toggle_entries = G_N_ELEMENTS (rb_shell_player_toggle_entries);
331+
332+static guint rb_shell_player_signals[LAST_SIGNAL] = { 0 };
333+
334+G_DEFINE_TYPE (RBShellPlayer, rb_shell_player, G_TYPE_OBJECT)
335+
336+static void
337+rb_shell_player_class_init (RBShellPlayerClass *klass)
338+{
339+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
340+
341+ object_class->dispose = rb_shell_player_dispose;
342+ object_class->finalize = rb_shell_player_finalize;
343+ object_class->constructed = rb_shell_player_constructed;
344+
345+ object_class->set_property = rb_shell_player_set_property;
346+ object_class->get_property = rb_shell_player_get_property;
347+
348+ /**
349+ * RBShellPlayer:source:
350+ *
351+ * The current source that is selected for playback.
352+ */
353+ g_object_class_install_property (object_class,
354+ PROP_SOURCE,
355+ g_param_spec_object ("source",
356+ "RBSource",
357+ "RBSource object",
358+ RB_TYPE_SOURCE,
359+ G_PARAM_READWRITE));
360+
361+ /**
362+ * RBShellPlayer:ui-manager:
363+ *
364+ * The GtkUIManager
365+ */
366+ g_object_class_install_property (object_class,
367+ PROP_UI_MANAGER,
368+ g_param_spec_object ("ui-manager",
369+ "GtkUIManager",
370+ "GtkUIManager object",
371+ GTK_TYPE_UI_MANAGER,
372+ G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
373+
374+ /**
375+ * RBShellPlayer:db:
376+ *
377+ * The #RhythmDB
378+ */
379+ g_object_class_install_property (object_class,
380+ PROP_DB,
381+ g_param_spec_object ("db",
382+ "RhythmDB",
383+ "RhythmDB object",
384+ RHYTHMDB_TYPE,
385+ G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
386+
387+ /**
388+ * RBShellPlayer:action-group:
389+ *
390+ * The #GtkActionGroup to use for player actions
391+ */
392+ g_object_class_install_property (object_class,
393+ PROP_ACTION_GROUP,
394+ g_param_spec_object ("action-group",
395+ "GtkActionGroup",
396+ "GtkActionGroup object",
397+ GTK_TYPE_ACTION_GROUP,
398+ G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
399+
400+ /**
401+ * RBShellPlayer:queue-source:
402+ *
403+ * The play queue source
404+ */
405+ g_object_class_install_property (object_class,
406+ PROP_QUEUE_SOURCE,
407+ g_param_spec_object ("queue-source",
408+ "RBPlayQueueSource",
409+ "RBPlayQueueSource object",
410+ RB_TYPE_PLAYLIST_SOURCE,
411+ G_PARAM_READWRITE));
412+
413+ /**
414+ * RBShellPlayer:queue-only:
415+ *
416+ * If %TRUE, activating an entry should only add it to the play queue.
417+ */
418+ g_object_class_install_property (object_class,
419+ PROP_QUEUE_ONLY,
420+ g_param_spec_boolean ("queue-only",
421+ "Queue only",
422+ "Activation only adds to queue",
423+ FALSE,
424+ G_PARAM_READWRITE));
425+
426+ /**
427+ * RBShellPlayer:playing-from-queue:
428+ *
429+ * If %TRUE, the current playing entry came from the play queue.
430+ */
431+ g_object_class_install_property (object_class,
432+ PROP_PLAYING_FROM_QUEUE,
433+ g_param_spec_boolean ("playing-from-queue",
434+ "Playing from queue",
435+ "Whether playing from the play queue or not",
436+ FALSE,
437+ G_PARAM_READABLE));
438+
439+ /**
440+ * RBShellPlayer:player:
441+ *
442+ * The player backend object (an object implementing the #RBPlayer interface).
443+ */
444+ g_object_class_install_property (object_class,
445+ PROP_PLAYER,
446+ g_param_spec_object ("player",
447+ "RBPlayer",
448+ "RBPlayer object",
449+ G_TYPE_OBJECT,
450+ G_PARAM_READABLE));
451+
452+ /**
453+ * RBShellPlayer:play-order:
454+ *
455+ * The current play order object.
456+ */
457+ g_object_class_install_property (object_class,
458+ PROP_PLAY_ORDER,
459+ g_param_spec_string ("play-order",
460+ "play-order",
461+ "What play order to use",
462+ "linear",
463+ G_PARAM_READABLE));
464+ /**
465+ * RBShellPlayer:playing:
466+ *
467+ * Whether Rhythmbox is currently playing something
468+ */
469+ g_object_class_install_property (object_class,
470+ PROP_PLAYING,
471+ g_param_spec_boolean ("playing",
472+ "playing",
473+ "Whether Rhythmbox is currently playing",
474+ FALSE,
475+ G_PARAM_READABLE));
476+ /**
477+ * RBShellPlayer:volume:
478+ *
479+ * The current playback volume (between 0.0 and 1.0)
480+ */
481+ g_object_class_install_property (object_class,
482+ PROP_VOLUME,
483+ g_param_spec_float ("volume",
484+ "volume",
485+ "Current playback volume",
486+ 0.0f, 1.0f, 1.0f,
487+ G_PARAM_READWRITE));
488+
489+ /**
490+ * RBShellPlayer:header:
491+ *
492+ * The #RBHeader object
493+ */
494+ g_object_class_install_property (object_class,
495+ PROP_HEADER,
496+ g_param_spec_object ("header",
497+ "RBHeader",
498+ "RBHeader object",
499+ RB_TYPE_HEADER,
500+ G_PARAM_READWRITE));
501+ /**
502+ * RBShellPlayer:mute:
503+ *
504+ * Whether playback is currently muted.
505+ */
506+ g_object_class_install_property (object_class,
507+ PROP_MUTE,
508+ g_param_spec_boolean ("mute",
509+ "mute",
510+ "Whether playback is muted",
511+ FALSE,
512+ G_PARAM_READWRITE));
513+ /**
514+ * RBShellPlayer:has-next:
515+ *
516+ * Whether there is a track to play after the current track.
517+ */
518+ g_object_class_install_property (object_class,
519+ PROP_HAS_NEXT,
520+ g_param_spec_boolean ("has-next",
521+ "has-next",
522+ "Whether there is a next track",
523+ FALSE,
524+ G_PARAM_READABLE));
525+ /**
526+ * RBShellPlayer:has-prev:
527+ *
528+ * Whether there was a previous track before the current track.
529+ */
530+ g_object_class_install_property (object_class,
531+ PROP_HAS_PREV,
532+ g_param_spec_boolean ("has-prev",
533+ "has-prev",
534+ "Whether there is a previous track",
535+ FALSE,
536+ G_PARAM_READABLE));
537+
538+ /**
539+ * RBShellPlayer::window-title-changed:
540+ * @player: the #RBShellPlayer
541+ * @title: the new window title
542+ *
543+ * Emitted when the main window title text should be changed
544+ */
545+ rb_shell_player_signals[WINDOW_TITLE_CHANGED] =
546+ g_signal_new ("window_title_changed",
547+ G_OBJECT_CLASS_TYPE (object_class),
548+ G_SIGNAL_RUN_LAST,
549+ G_STRUCT_OFFSET (RBShellPlayerClass, window_title_changed),
550+ NULL, NULL,
551+ g_cclosure_marshal_VOID__STRING,
552+ G_TYPE_NONE,
553+ 1,
554+ G_TYPE_STRING);
555+
556+ /**
557+ * RBShellPlayer::elapsed-changed:
558+ * @player: the #RBShellPlayer
559+ * @elapsed: the new playback position in seconds
560+ *
561+ * Emitted when the playback position changes.
562+ */
563+ rb_shell_player_signals[ELAPSED_CHANGED] =
564+ g_signal_new ("elapsed_changed",
565+ G_OBJECT_CLASS_TYPE (object_class),
566+ G_SIGNAL_RUN_LAST,
567+ G_STRUCT_OFFSET (RBShellPlayerClass, elapsed_changed),
568+ NULL, NULL,
569+ g_cclosure_marshal_VOID__UINT,
570+ G_TYPE_NONE,
571+ 1,
572+ G_TYPE_UINT);
573+
574+ /**
575+ * RBShellPlayer::playing-source-changed:
576+ * @player: the #RBShellPlayer
577+ * @source: the #RBSource that is now playing
578+ *
579+ * Emitted when a new #RBSource instance starts playing
580+ */
581+ rb_shell_player_signals[PLAYING_SOURCE_CHANGED] =
582+ g_signal_new ("playing-source-changed",
583+ G_OBJECT_CLASS_TYPE (object_class),
584+ G_SIGNAL_RUN_LAST,
585+ G_STRUCT_OFFSET (RBShellPlayerClass, playing_source_changed),
586+ NULL, NULL,
587+ g_cclosure_marshal_VOID__OBJECT,
588+ G_TYPE_NONE,
589+ 1,
590+ RB_TYPE_SOURCE);
591+
592+ /**
593+ * RBShellPlayer::playing-changed:
594+ * @player: the #RBShellPlayer
595+ * @playing: flag indicating playback state
596+ *
597+ * Emitted when playback either stops or starts.
598+ */
599+ rb_shell_player_signals[PLAYING_CHANGED] =
600+ g_signal_new ("playing-changed",
601+ G_OBJECT_CLASS_TYPE (object_class),
602+ G_SIGNAL_RUN_LAST,
603+ G_STRUCT_OFFSET (RBShellPlayerClass, playing_changed),
604+ NULL, NULL,
605+ g_cclosure_marshal_VOID__BOOLEAN,
606+ G_TYPE_NONE,
607+ 1,
608+ G_TYPE_BOOLEAN);
609+
610+ /**
611+ * RBShellPlayer::playing-song-changed:
612+ * @player: the #RBShellPlayer
613+ * @entry: the new playing #RhythmDBEntry
614+ *
615+ * Emitted when the playing database entry changes
616+ */
617+ rb_shell_player_signals[PLAYING_SONG_CHANGED] =
618+ g_signal_new ("playing-song-changed",
619+ G_OBJECT_CLASS_TYPE (object_class),
620+ G_SIGNAL_RUN_LAST,
621+ G_STRUCT_OFFSET (RBShellPlayerClass, playing_song_changed),
622+ NULL, NULL,
623+ g_cclosure_marshal_VOID__BOXED,
624+ G_TYPE_NONE,
625+ 1,
626+ RHYTHMDB_TYPE_ENTRY);
627+
628+ /**
629+ * RBShellPlayer::playing-uri-changed:
630+ * @player: the #RBShellPlayer
631+ * @uri: the URI of the new playing entry
632+ *
633+ * Emitted when the playing database entry changes, providing the
634+ * URI of the entry.
635+ */
636+ rb_shell_player_signals[PLAYING_URI_CHANGED] =
637+ g_signal_new ("playing-uri-changed",
638+ G_OBJECT_CLASS_TYPE (object_class),
639+ G_SIGNAL_RUN_LAST,
640+ G_STRUCT_OFFSET (RBShellPlayerClass, playing_uri_changed),
641+ NULL, NULL,
642+ g_cclosure_marshal_VOID__STRING,
643+ G_TYPE_NONE,
644+ 1,
645+ G_TYPE_STRING);
646+
647+ /**
648+ * RBShellPlayer::playing-song-property-changed:
649+ * @player: the #RBShellPlayer
650+ * @uri: the URI of the playing entry
651+ * @property: the name of the property that changed
652+ * @old: the previous value for the property
653+ * @newvalue: the new value of the property
654+ *
655+ * Emitted when a property of the playing database entry changes.
656+ */
657+ rb_shell_player_signals[PLAYING_SONG_PROPERTY_CHANGED] =
658+ g_signal_new ("playing-song-property-changed",
659+ G_OBJECT_CLASS_TYPE (object_class),
660+ G_SIGNAL_RUN_LAST,
661+ G_STRUCT_OFFSET (RBShellPlayerClass, playing_song_property_changed),
662+ NULL, NULL,
663+ rb_marshal_VOID__STRING_STRING_POINTER_POINTER,
664+ G_TYPE_NONE,
665+ 4,
666+ G_TYPE_STRING, G_TYPE_STRING,
667+ G_TYPE_VALUE, G_TYPE_VALUE);
668+
669+ /**
670+ * RBShellPlayer::elapsed-nano-changed:
671+ * @player: the #RBShellPlayer
672+ * @elapsed: the new playback position in nanoseconds
673+ *
674+ * Emitted when the playback position changes. Only use this (as opposed to
675+ * elapsed-changed) when you require subsecond precision. This signal will be
676+ * emitted multiple times per second.
677+ */
678+ rb_shell_player_signals[ELAPSED_NANO_CHANGED] =
679+ g_signal_new ("elapsed-nano-changed",
680+ G_OBJECT_CLASS_TYPE (object_class),
681+ G_SIGNAL_RUN_LAST,
682+ G_STRUCT_OFFSET (RBShellPlayerClass, elapsed_nano_changed),
683+ NULL, NULL,
684+ rb_marshal_VOID__INT64,
685+ G_TYPE_NONE,
686+ 1,
687+ G_TYPE_INT64);
688+
689+ g_type_class_add_private (klass, sizeof (RBShellPlayerPrivate));
690+}
691+
692+static void
693+rb_shell_player_constructed (GObject *object)
694+{
695+ RBShellPlayer *player;
696+ GtkAction *action;
697+
698+ RB_CHAIN_GOBJECT_METHOD (rb_shell_player_parent_class, constructed, object);
699+
700+ player = RB_SHELL_PLAYER (object);
701+
702+ gtk_action_group_add_actions (player->priv->actiongroup,
703+ rb_shell_player_actions,
704+ rb_shell_player_n_actions,
705+ player);
706+ gtk_action_group_add_toggle_actions (player->priv->actiongroup,
707+ rb_shell_player_toggle_entries,
708+ rb_shell_player_n_toggle_entries,
709+ player);
710+
711+ player_settings_changed_cb (player->priv->settings, "transition-time", player);
712+ player_settings_changed_cb (player->priv->settings, "play-order", player);
713+
714+
715+ action = gtk_action_group_get_action (player->priv->actiongroup, "ControlPrevious");
716+ g_object_bind_property (player, "has-prev", action, "sensitive", G_BINDING_DEFAULT);
717+ action = gtk_action_group_get_action (player->priv->actiongroup, "ControlNext");
718+ g_object_bind_property (player, "has-next", action, "sensitive", G_BINDING_DEFAULT);
719+
720+ player->priv->syncing_state = TRUE;
721+ rb_shell_player_set_playing_source (player, NULL);
722+ rb_shell_player_sync_play_order (player);
723+ rb_shell_player_sync_control_state (player);
724+ rb_shell_player_sync_volume (player, FALSE, TRUE);
725+ player->priv->syncing_state = FALSE;
726+
727+ g_signal_connect (player,
728+ "notify::playing",
729+ G_CALLBACK (rb_shell_player_playing_changed_cb),
730+ NULL);
731+}
732+
733+static void
734+volume_pre_unmount_cb (GVolumeMonitor *monitor,
735+ GMount *mount,
736+ RBShellPlayer *player)
737+{
738+ const char *entry_mount_point;
739+ GFile *mount_root;
740+ RhythmDBEntry *entry;
741+
742+ entry = rb_shell_player_get_playing_entry (player);
743+ if (entry == NULL) {
744+ return;
745+ }
746+
747+ entry_mount_point = rhythmdb_entry_get_string (entry, RHYTHMDB_PROP_MOUNTPOINT);
748+ if (entry_mount_point == NULL) {
749+ return;
750+ }
751+
752+ mount_root = g_mount_get_root (mount);
753+ if (mount_root != NULL) {
754+ char *mount_point;
755+
756+ mount_point = g_file_get_uri (mount_root);
757+ if (mount_point && entry_mount_point &&
758+ strcmp (entry_mount_point, mount_point) == 0) {
759+ rb_shell_player_stop (player);
760+ }
761+
762+ g_free (mount_point);
763+ g_object_unref (mount_root);
764+ }
765+
766+ rhythmdb_entry_unref (entry);
767+}
768+
769+static void
770+reemit_playing_signal (RBShellPlayer *player,
771+ GParamSpec *pspec,
772+ gpointer data)
773+{
774+ g_signal_emit (player, rb_shell_player_signals[PLAYING_CHANGED], 0,
775+ rb_player_playing (player->priv->mmplayer));
776+}
777+
778+static void
779+rb_shell_player_open_playlist_url (RBShellPlayer *player,
780+ const char *location,
781+ RhythmDBEntry *entry,
782+ RBPlayerPlayType play_type)
783+{
784+ GError *error = NULL;
785+
786+ rb_debug ("playing stream url %s", location);
787+ rb_player_open (player->priv->mmplayer,
788+ location,
789+ rhythmdb_entry_ref (entry),
790+ (GDestroyNotify) rhythmdb_entry_unref,
791+ &error);
792+ if (error == NULL)
793+ rb_player_play (player->priv->mmplayer, play_type, player->priv->track_transition_time, &error);
794+
795+ if (error) {
796+ GDK_THREADS_ENTER ();
797+ rb_shell_player_error (player, TRUE, error);
798+ g_error_free (error);
799+ GDK_THREADS_LEAVE ();
800+ }
801+}
802+
803+static void
804+rb_shell_player_handle_eos_unlocked (RBShellPlayer *player, RhythmDBEntry *entry, gboolean allow_stop)
805+{
806+ RBSource *source;
807+ gboolean update_stats;
808+ gboolean dragging;
809+
810+ source = player->priv->current_playing_source;
811+
812+ /* nothing to do */
813+ if (source == NULL) {
814+ return;
815+ }
816+
817+ if (player->priv->playing_entry_eos) {
818+ rb_debug ("playing entry has already EOS'd");
819+ return;
820+ }
821+
822+ if (entry != NULL) {
823+ if (player->priv->playing_entry != entry) {
824+ rb_debug ("EOS'd entry is not the current playing entry; ignoring");
825+ return;
826+ }
827+
828+ rhythmdb_entry_ref (entry);
829+ }
830+
831+ /* defer EOS handling while the position slider is being dragged */
832+ g_object_get (player->priv->header_widget, "slider-dragging", &dragging, NULL);
833+ if (dragging) {
834+ rb_debug ("slider is dragging, will handle EOS (if applicable) on release");
835+ player->priv->playing_entry_eos = TRUE;
836+ if (entry != NULL)
837+ rhythmdb_entry_unref (entry);
838+ return;
839+ }
840+
841+ update_stats = FALSE;
842+ switch (rb_source_handle_eos (source)) {
843+ case RB_SOURCE_EOF_ERROR:
844+ if (allow_stop) {
845+ rb_error_dialog (NULL, _("Stream error"),
846+ _("Unexpected end of stream!"));
847+ rb_shell_player_stop (player);
848+ player->priv->playing_entry_eos = TRUE;
849+ update_stats = TRUE;
850+ }
851+ break;
852+ case RB_SOURCE_EOF_STOP:
853+ if (allow_stop) {
854+ rb_shell_player_stop (player);
855+ player->priv->playing_entry_eos = TRUE;
856+ update_stats = TRUE;
857+ }
858+ break;
859+ case RB_SOURCE_EOF_RETRY: {
860+ GTimeVal current;
861+ gint diff;
862+
863+ g_get_current_time (&current);
864+ diff = current.tv_sec - player->priv->last_retry.tv_sec;
865+ player->priv->last_retry = current;
866+
867+ if (rb_source_try_playlist (source) &&
868+ !g_queue_is_empty (player->priv->playlist_urls)) {
869+ char *location = g_queue_pop_head (player->priv->playlist_urls);
870+ rb_debug ("trying next radio stream url: %s", location);
871+
872+ /* we're handling an unexpected EOS here, so crossfading isn't
873+ * really possible anyway -> specify FALSE.
874+ */
875+ rb_shell_player_open_playlist_url (player, location, entry, FALSE);
876+ g_free (location);
877+ break;
878+ }
879+
880+ if (allow_stop) {
881+ if (diff < 4) {
882+ rb_debug ("Last retry was less than 4 seconds ago...aborting retry playback");
883+ rb_shell_player_stop (player);
884+ } else {
885+ rb_shell_player_play_entry (player, entry, NULL);
886+ }
887+ player->priv->playing_entry_eos = TRUE;
888+ update_stats = TRUE;
889+ }
890+ }
891+ break;
892+ case RB_SOURCE_EOF_NEXT:
893+ {
894+ GError *error = NULL;
895+
896+ player->priv->playing_entry_eos = TRUE;
897+ update_stats = TRUE;
898+ if (!rb_shell_player_do_next_internal (player, TRUE, allow_stop, &error)) {
899+ if (error->domain != RB_SHELL_PLAYER_ERROR ||
900+ error->code != RB_SHELL_PLAYER_ERROR_END_OF_PLAYLIST) {
901+ g_warning ("Unhandled error: %s", error->message);
902+ } else if (allow_stop == FALSE) {
903+ /* handle the real EOS when it happens */
904+ player->priv->playing_entry_eos = FALSE;
905+ update_stats = FALSE;
906+ }
907+ }
908+ }
909+ break;
910+ }
911+
912+ if (update_stats &&
913+ rhythmdb_entry_get_string (entry, RHYTHMDB_PROP_PLAYBACK_ERROR) == NULL) {
914+ rb_debug ("updating play statistics");
915+ rb_source_update_play_statistics (source,
916+ player->priv->db,
917+ entry);
918+ }
919+
920+ if (entry != NULL)
921+ rhythmdb_entry_unref (entry);
922+}
923+
924+static void
925+rb_shell_player_slider_dragging_cb (GObject *header, GParamSpec *pspec, RBShellPlayer *player)
926+{
927+ gboolean drag;
928+
929+ g_object_get (player->priv->header_widget, "slider-dragging", &drag, NULL);
930+ rb_debug ("slider dragging? %d", drag);
931+
932+ /* if an EOS occurred while dragging, process it now */
933+ if (drag == FALSE && player->priv->playing_entry_eos) {
934+ rb_debug ("processing EOS delayed due to slider dragging");
935+ player->priv->playing_entry_eos = FALSE;
936+ rb_shell_player_handle_eos_unlocked (player, rb_shell_player_get_playing_entry (player), FALSE);
937+ }
938+}
939+
940+static void
941+rb_shell_player_handle_eos (RBPlayer *player,
942+ RhythmDBEntry *entry,
943+ gboolean early,
944+ RBShellPlayer *shell_player)
945+{
946+ const char *location;
947+ if (entry == NULL) {
948+ /* special case: this is called with entry == NULL to simulate an EOS
949+ * from the current playing entry.
950+ */
951+ entry = shell_player->priv->playing_entry;
952+ if (entry == NULL) {
953+ rb_debug ("called to simulate EOS for playing entry, but nothing is playing");
954+ return;
955+ }
956+ }
957+
958+ GDK_THREADS_ENTER ();
959+
960+ location = rhythmdb_entry_get_string (entry, RHYTHMDB_PROP_LOCATION);
961+ if (entry != shell_player->priv->playing_entry) {
962+ rb_debug ("got unexpected eos for %s", location);
963+ } else {
964+ rb_debug ("handling eos for %s", location);
965+ /* don't allow playback to be stopped on early EOS notifications */
966+ rb_shell_player_handle_eos_unlocked (shell_player, entry, (early == FALSE));
967+ }
968+
969+ GDK_THREADS_LEAVE ();
970+}
971+
972+
973+static void
974+rb_shell_player_handle_redirect (RBPlayer *player,
975+ RhythmDBEntry *entry,
976+ const gchar *uri,
977+ RBShellPlayer *shell_player)
978+{
979+ GValue val = { 0 };
980+
981+ rb_debug ("redirect to %s", uri);
982+
983+ /* Stop existing stream */
984+ rb_player_close (shell_player->priv->mmplayer, NULL, NULL);
985+
986+ /* Update entry */
987+ g_value_init (&val, G_TYPE_STRING);
988+ g_value_set_string (&val, uri);
989+ rhythmdb_entry_set (shell_player->priv->db, entry, RHYTHMDB_PROP_LOCATION, &val);
990+ g_value_unset (&val);
991+ rhythmdb_commit (shell_player->priv->db);
992+
993+ /* Play new URI */
994+ rb_shell_player_open_location (shell_player, entry, RB_PLAYER_PLAY_REPLACE, NULL);
995+}
996+
997+static void
998+rb_shell_player_init (RBShellPlayer *player)
999+{
1000+ GError *error = NULL;
1001+
1002+ player->priv = RB_SHELL_PLAYER_GET_PRIVATE (player);
1003+
1004+ player->priv->settings = g_settings_new ("org.gnome.rhythmbox.player");
1005+ player->priv->ui_settings = g_settings_new ("org.gnome.rhythmbox");
1006+ g_signal_connect_object (player->priv->settings,
1007+ "changed",
1008+ G_CALLBACK (player_settings_changed_cb),
1009+ player, 0);
1010+
1011+ player->priv->play_orders = g_hash_table_new_full (g_str_hash, g_str_equal, NULL, (GDestroyNotify)_play_order_description_free);
1012+
1013+ rb_shell_player_add_play_order (player, "linear", N_("Linear"),
1014+ RB_TYPE_LINEAR_PLAY_ORDER, FALSE);
1015+ rb_shell_player_add_play_order (player, "linear-loop", N_("Linear looping"),
1016+ RB_TYPE_LINEAR_PLAY_ORDER_LOOP, FALSE);
1017+ rb_shell_player_add_play_order (player, "shuffle", N_("Shuffle"),
1018+ RB_TYPE_SHUFFLE_PLAY_ORDER, FALSE);
1019+ rb_shell_player_add_play_order (player, "random-equal-weights", N_("Random with equal weights"),
1020+ RB_TYPE_RANDOM_PLAY_ORDER_EQUAL_WEIGHTS, FALSE);
1021+ rb_shell_player_add_play_order (player, "random-by-age", N_("Random by time since last play"),
1022+ RB_TYPE_RANDOM_PLAY_ORDER_BY_AGE, FALSE);
1023+ rb_shell_player_add_play_order (player, "random-by-rating", N_("Random by rating"),
1024+ RB_TYPE_RANDOM_PLAY_ORDER_BY_RATING, FALSE);
1025+ rb_shell_player_add_play_order (player, "random-by-age-and-rating", N_("Random by time since last play and rating"),
1026+ RB_TYPE_RANDOM_PLAY_ORDER_BY_AGE_AND_RATING, FALSE);
1027+ rb_shell_player_add_play_order (player, "queue", N_("Linear, removing entries once played"),
1028+ RB_TYPE_QUEUE_PLAY_ORDER, TRUE);
1029+
1030+ player->priv->mmplayer = rb_player_new (g_settings_get_boolean (player->priv->settings, "use-xfade-backend"),
1031+ &error);
1032+ if (error != NULL) {
1033+ GtkWidget *dialog;
1034+ dialog = gtk_message_dialog_new (NULL, GTK_DIALOG_MODAL,
1035+ GTK_MESSAGE_ERROR,
1036+ GTK_BUTTONS_CLOSE,
1037+ _("Failed to create the player: %s"),
1038+ error->message);
1039+ gtk_dialog_run (GTK_DIALOG (dialog));
1040+ exit (1);
1041+ }
1042+
1043+ g_signal_connect_object (player->priv->mmplayer,
1044+ "eos",
1045+ G_CALLBACK (rb_shell_player_handle_eos),
1046+ player, 0);
1047+
1048+ g_signal_connect_object (player->priv->mmplayer,
1049+ "redirect",
1050+ G_CALLBACK (rb_shell_player_handle_redirect),
1051+ player, 0);
1052+
1053+ g_signal_connect_object (player->priv->mmplayer,
1054+ "tick",
1055+ G_CALLBACK (tick_cb),
1056+ player, 0);
1057+
1058+ g_signal_connect_object (player->priv->mmplayer,
1059+ "error",
1060+ G_CALLBACK (error_cb),
1061+ player, 0);
1062+
1063+ g_signal_connect_object (player->priv->mmplayer,
1064+ "playing-stream",
1065+ G_CALLBACK (playing_stream_cb),
1066+ player, 0);
1067+
1068+ g_signal_connect_object (player->priv->mmplayer,
1069+ "missing-plugins",
1070+ G_CALLBACK (missing_plugins_cb),
1071+ player, 0);
1072+ g_signal_connect_object (player->priv->mmplayer,
1073+ "volume-changed",
1074+ G_CALLBACK (rb_shell_player_volume_changed_cb),
1075+ player, 0);
1076+
1077+ g_signal_connect_object (player->priv->mmplayer,
1078+ "image",
1079+ G_CALLBACK (player_image_cb),
1080+ player, 0);
1081+
1082+ {
1083+ GVolumeMonitor *monitor = g_volume_monitor_get ();
1084+ g_signal_connect (G_OBJECT (monitor),
1085+ "mount-pre-unmount",
1086+ G_CALLBACK (volume_pre_unmount_cb),
1087+ player);
1088+ g_object_unref (monitor); /* hmm */
1089+ }
1090+
1091+ player->priv->volume = g_settings_get_double (player->priv->settings, "volume");
1092+
1093+ g_signal_connect (player, "notify::playing",
1094+ G_CALLBACK (reemit_playing_signal), NULL);
1095+}
1096+
1097+static void
1098+rb_shell_player_set_source_internal (RBShellPlayer *player,
1099+ RBSource *source)
1100+{
1101+ if (player->priv->selected_source != NULL) {
1102+ RBEntryView *songs = rb_source_get_entry_view (player->priv->selected_source);
1103+ GList *property_views = rb_source_get_property_views (player->priv->selected_source);
1104+ GList *l;
1105+
1106+ if (songs != NULL) {
1107+ g_signal_handlers_disconnect_by_func (G_OBJECT (songs),
1108+ G_CALLBACK (rb_shell_player_entry_activated_cb),
1109+ player);
1110+ }
1111+
1112+ for (l = property_views; l != NULL; l = g_list_next (l)) {
1113+ g_signal_handlers_disconnect_by_func (G_OBJECT (l->data),
1114+ G_CALLBACK (rb_shell_player_property_row_activated_cb),
1115+ player);
1116+ }
1117+
1118+ g_list_free (property_views);
1119+ }
1120+
1121+ player->priv->selected_source = source;
1122+
1123+ rb_debug ("selected source %p", player->priv->selected_source);
1124+
1125+ rb_shell_player_sync_with_selected_source (player);
1126+ rb_shell_player_sync_buttons (player);
1127+
1128+ if (player->priv->selected_source != NULL) {
1129+ RBEntryView *songs = rb_source_get_entry_view (player->priv->selected_source);
1130+ GList *property_views = rb_source_get_property_views (player->priv->selected_source);
1131+ GList *l;
1132+
1133+ if (songs)
1134+ g_signal_connect_object (G_OBJECT (songs),
1135+ "entry-activated",
1136+ G_CALLBACK (rb_shell_player_entry_activated_cb),
1137+ player, 0);
1138+ for (l = property_views; l != NULL; l = g_list_next (l)) {
1139+ g_signal_connect_object (G_OBJECT (l->data),
1140+ "property-activated",
1141+ G_CALLBACK (rb_shell_player_property_row_activated_cb),
1142+ player, 0);
1143+ }
1144+
1145+ g_list_free (property_views);
1146+ }
1147+
1148+ /* If we're not playing, change the play order's view of the current source;
1149+ * if the selected source is the queue, however, set it to NULL so it'll stop
1150+ * once the queue is empty.
1151+ */
1152+ if (player->priv->current_playing_source == NULL) {
1153+ RBPlayOrder *porder = NULL;
1154+ RBSource *source = player->priv->selected_source;
1155+ if (source == RB_SOURCE (player->priv->queue_source)) {
1156+ source = NULL;
1157+ } else if (source != NULL) {
1158+ g_object_get (source, "play-order", &porder, NULL);
1159+ }
1160+
1161+ if (porder == NULL)
1162+ porder = g_object_ref (player->priv->play_order);
1163+
1164+ rb_play_order_playing_source_changed (porder, source);
1165+ g_object_unref (porder);
1166+ }
1167+}
1168+
1169+static void
1170+rb_shell_player_set_db_internal (RBShellPlayer *player,
1171+ RhythmDB *db)
1172+{
1173+ if (player->priv->db != NULL) {
1174+ g_signal_handlers_disconnect_by_func (player->priv->db,
1175+ G_CALLBACK (rb_shell_player_entry_changed_cb),
1176+ player);
1177+ g_signal_handlers_disconnect_by_func (player->priv->db,
1178+ G_CALLBACK (rb_shell_player_extra_metadata_cb),
1179+ player);
1180+ }
1181+
1182+ player->priv->db = db;
1183+
1184+ if (player->priv->db != NULL) {
1185+ /* Listen for changed entries to update metadata display */
1186+ g_signal_connect_object (G_OBJECT (player->priv->db),
1187+ "entry_changed",
1188+ G_CALLBACK (rb_shell_player_entry_changed_cb),
1189+ player, 0);
1190+ g_signal_connect_object (G_OBJECT (player->priv->db),
1191+ "entry_extra_metadata_notify",
1192+ G_CALLBACK (rb_shell_player_extra_metadata_cb),
1193+ player, 0);
1194+ }
1195+}
1196+
1197+static void
1198+rb_shell_player_set_queue_source_internal (RBShellPlayer *player,
1199+ RBPlayQueueSource *source)
1200+{
1201+ if (player->priv->queue_source != NULL) {
1202+ RBEntryView *sidebar;
1203+
1204+ g_object_get (player->priv->queue_source, "sidebar", &sidebar, NULL);
1205+ g_signal_handlers_disconnect_by_func (sidebar,
1206+ G_CALLBACK (rb_shell_player_entry_activated_cb),
1207+ player);
1208+ g_object_unref (sidebar);
1209+
1210+ if (player->priv->queue_play_order != NULL) {
1211+ g_signal_handlers_disconnect_by_func (player->priv->queue_play_order,
1212+ G_CALLBACK (rb_shell_player_play_order_update_cb),
1213+ player);
1214+ g_object_unref (player->priv->queue_play_order);
1215+ }
1216+
1217+ }
1218+
1219+ player->priv->queue_source = source;
1220+
1221+ if (player->priv->queue_source != NULL) {
1222+ RBEntryView *sidebar;
1223+
1224+ g_object_get (player->priv->queue_source, "play-order", &player->priv->queue_play_order, NULL);
1225+
1226+ g_signal_connect_object (G_OBJECT (player->priv->queue_play_order),
1227+ "have_next_previous_changed",
1228+ G_CALLBACK (rb_shell_player_play_order_update_cb),
1229+ player, 0);
1230+ rb_shell_player_play_order_update_cb (player->priv->play_order,
1231+ FALSE, FALSE,
1232+ player);
1233+ rb_play_order_playing_source_changed (player->priv->queue_play_order,
1234+ RB_SOURCE (player->priv->queue_source));
1235+
1236+ g_object_get (player->priv->queue_source, "sidebar", &sidebar, NULL);
1237+ g_signal_connect_object (G_OBJECT (sidebar),
1238+ "entry-activated",
1239+ G_CALLBACK (rb_shell_player_entry_activated_cb),
1240+ player, 0);
1241+ g_object_unref (sidebar);
1242+ }
1243+}
1244+
1245+static void
1246+rb_shell_player_dispose (GObject *object)
1247+{
1248+ RBShellPlayer *player;
1249+
1250+ g_return_if_fail (object != NULL);
1251+ g_return_if_fail (RB_IS_SHELL_PLAYER (object));
1252+
1253+ player = RB_SHELL_PLAYER (object);
1254+
1255+ g_return_if_fail (player->priv != NULL);
1256+
1257+ if (player->priv->ui_settings != NULL) {
1258+ g_object_unref (player->priv->ui_settings);
1259+ player->priv->ui_settings = NULL;
1260+ }
1261+
1262+ if (player->priv->settings != NULL) {
1263+ /* hm, is this really the place to do this? */
1264+ g_settings_set_double (player->priv->settings,
1265+ "volume",
1266+ player->priv->volume);
1267+
1268+ g_object_unref (player->priv->settings);
1269+ player->priv->settings = NULL;
1270+ }
1271+
1272+ if (player->priv->mmplayer != NULL) {
1273+ g_object_unref (player->priv->mmplayer);
1274+ player->priv->mmplayer = NULL;
1275+ }
1276+
1277+ if (player->priv->play_order != NULL) {
1278+ g_object_unref (player->priv->play_order);
1279+ player->priv->play_order = NULL;
1280+ }
1281+
1282+ if (player->priv->queue_play_order != NULL) {
1283+ g_object_unref (player->priv->queue_play_order);
1284+ player->priv->queue_play_order = NULL;
1285+ }
1286+
1287+ if (player->priv->do_next_idle_id != 0) {
1288+ g_source_remove (player->priv->do_next_idle_id);
1289+ player->priv->do_next_idle_id = 0;
1290+ }
1291+
1292+ if (player->priv->unblock_play_id != 0) {
1293+ g_source_remove (player->priv->unblock_play_id);
1294+ player->priv->unblock_play_id = 0;
1295+ }
1296+
1297+ G_OBJECT_CLASS (rb_shell_player_parent_class)->dispose (object);
1298+}
1299+
1300+static void
1301+rb_shell_player_finalize (GObject *object)
1302+{
1303+ RBShellPlayer *player;
1304+
1305+ g_return_if_fail (object != NULL);
1306+ g_return_if_fail (RB_IS_SHELL_PLAYER (object));
1307+
1308+ player = RB_SHELL_PLAYER (object);
1309+
1310+ g_return_if_fail (player->priv != NULL);
1311+
1312+ g_hash_table_destroy (player->priv->play_orders);
1313+
1314+ G_OBJECT_CLASS (rb_shell_player_parent_class)->finalize (object);
1315+}
1316+
1317+static void
1318+rb_shell_player_set_property (GObject *object,
1319+ guint prop_id,
1320+ const GValue *value,
1321+ GParamSpec *pspec)
1322+{
1323+ RBShellPlayer *player = RB_SHELL_PLAYER (object);
1324+
1325+ switch (prop_id) {
1326+ case PROP_SOURCE:
1327+ rb_shell_player_set_source_internal (player, g_value_get_object (value));
1328+ break;
1329+ case PROP_UI_MANAGER:
1330+ player->priv->ui_manager = g_value_get_object (value);
1331+ break;
1332+ case PROP_DB:
1333+ rb_shell_player_set_db_internal (player, g_value_get_object (value));
1334+ break;
1335+ case PROP_ACTION_GROUP:
1336+ player->priv->actiongroup = g_value_get_object (value);
1337+ break;
1338+ case PROP_PLAY_ORDER:
1339+ g_settings_set_string (player->priv->settings,
1340+ "play-order",
1341+ g_value_get_string (value));
1342+ break;
1343+ case PROP_VOLUME:
1344+ player->priv->volume = g_value_get_float (value);
1345+ rb_shell_player_sync_volume (player, FALSE, TRUE);
1346+ break;
1347+ case PROP_HEADER:
1348+ player->priv->header_widget = g_value_get_object (value);
1349+ g_signal_connect_object (player->priv->header_widget,
1350+ "notify::slider-dragging",
1351+ G_CALLBACK (rb_shell_player_slider_dragging_cb),
1352+ player, 0);
1353+ break;
1354+ case PROP_QUEUE_SOURCE:
1355+ rb_shell_player_set_queue_source_internal (player, g_value_get_object (value));
1356+ break;
1357+ case PROP_QUEUE_ONLY:
1358+ player->priv->queue_only = g_value_get_boolean (value);
1359+ break;
1360+ case PROP_MUTE:
1361+ player->priv->mute = g_value_get_boolean (value);
1362+ rb_shell_player_sync_volume (player, FALSE, TRUE);
1363+ default:
1364+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
1365+ break;
1366+ }
1367+}
1368+
1369+static void
1370+rb_shell_player_get_property (GObject *object,
1371+ guint prop_id,
1372+ GValue *value,
1373+ GParamSpec *pspec)
1374+{
1375+ RBShellPlayer *player = RB_SHELL_PLAYER (object);
1376+
1377+ switch (prop_id) {
1378+ case PROP_SOURCE:
1379+ g_value_set_object (value, player->priv->selected_source);
1380+ break;
1381+ case PROP_UI_MANAGER:
1382+ g_value_set_object (value, player->priv->ui_manager);
1383+ break;
1384+ case PROP_DB:
1385+ g_value_set_object (value, player->priv->db);
1386+ break;
1387+ case PROP_ACTION_GROUP:
1388+ g_value_set_object (value, player->priv->actiongroup);
1389+ break;
1390+ case PROP_PLAY_ORDER:
1391+ {
1392+ char *play_order = g_settings_get_string (player->priv->settings,
1393+ "play-order");
1394+ if (play_order == NULL)
1395+ play_order = g_strdup ("linear");
1396+ g_value_take_string (value, play_order);
1397+ break;
1398+ }
1399+ case PROP_PLAYING:
1400+ if (player->priv->mmplayer != NULL)
1401+ g_value_set_boolean (value, rb_player_playing (player->priv->mmplayer));
1402+ else
1403+ g_value_set_boolean (value, FALSE);
1404+ break;
1405+ case PROP_VOLUME:
1406+ g_value_set_float (value, player->priv->volume);
1407+ break;
1408+ case PROP_HEADER:
1409+ g_value_set_object (value, player->priv->header_widget);
1410+ break;
1411+ case PROP_QUEUE_SOURCE:
1412+ g_value_set_object (value, player->priv->queue_source);
1413+ break;
1414+ case PROP_QUEUE_ONLY:
1415+ g_value_set_boolean (value, player->priv->queue_only);
1416+ break;
1417+ case PROP_PLAYING_FROM_QUEUE:
1418+ g_value_set_boolean (value, player->priv->current_playing_source == RB_SOURCE (player->priv->queue_source));
1419+ break;
1420+ case PROP_PLAYER:
1421+ g_value_set_object (value, player->priv->mmplayer);
1422+ break;
1423+ case PROP_MUTE:
1424+ g_value_set_boolean (value, player->priv->mute);
1425+ break;
1426+ case PROP_HAS_NEXT:
1427+ g_value_set_boolean (value, player->priv->has_next);
1428+ break;
1429+ case PROP_HAS_PREV:
1430+ g_value_set_boolean (value, player->priv->has_prev);
1431+ break;
1432+ default:
1433+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
1434+ break;
1435+ }
1436+}
1437+
1438+GQuark
1439+rb_shell_player_error_quark (void)
1440+{
1441+ static GQuark quark = 0;
1442+ if (!quark)
1443+ quark = g_quark_from_static_string ("rb_shell_player_error");
1444+
1445+ return quark;
1446+}
1447+
1448+/**
1449+ * rb_shell_player_set_selected_source:
1450+ * @player: the #RBShellPlayer
1451+ * @source: the #RBSource to select
1452+ *
1453+ * Updates the player to reflect a new source being selected.
1454+ */
1455+void
1456+rb_shell_player_set_selected_source (RBShellPlayer *player,
1457+ RBSource *source)
1458+{
1459+ g_return_if_fail (RB_IS_SHELL_PLAYER (player));
1460+ g_return_if_fail (source == NULL || RB_IS_SOURCE (source));
1461+
1462+ g_object_set (player, "source", source, NULL);
1463+}
1464+
1465+/**
1466+ * rb_shell_player_get_playing_source:
1467+ * @player: the #RBShellPlayer
1468+ *
1469+ * Retrieves the current playing source. That is, the source from
1470+ * which the current song was drawn. This differs from
1471+ * #rb_shell_player_get_active_source when the current song came
1472+ * from the play queue.
1473+ *
1474+ * Return value: (transfer none): the current playing #RBSource
1475+ */
1476+RBSource *
1477+rb_shell_player_get_playing_source (RBShellPlayer *player)
1478+{
1479+ return player->priv->current_playing_source;
1480+}
1481+
1482+/**
1483+ * rb_shell_player_get_active_source:
1484+ * @player: the #RBShellPlayer
1485+ *
1486+ * Retrieves the active source. This is the source that the user
1487+ * selected for playback.
1488+ *
1489+ * Return value: (transfer none): the active #RBSource
1490+ */
1491+RBSource *
1492+rb_shell_player_get_active_source (RBShellPlayer *player)
1493+{
1494+ return player->priv->source;
1495+}
1496+
1497+/**
1498+ * rb_shell_player_new:
1499+ * @db: the #RhythmDB
1500+ * @mgr: the #GtkUIManager
1501+ * @actiongroup: the #GtkActionGroup to use
1502+ *
1503+ * Creates the #RBShellPlayer
1504+ *
1505+ * Return value: the #RBShellPlayer instance
1506+ */
1507+RBShellPlayer *
1508+rb_shell_player_new (RhythmDB *db,
1509+ GtkUIManager *mgr,
1510+ GtkActionGroup *actiongroup)
1511+{
1512+ return g_object_new (RB_TYPE_SHELL_PLAYER,
1513+ "ui-manager", mgr,
1514+ "action-group", actiongroup,
1515+ "db", db,
1516+ NULL);
1517+}
1518+
1519+/**
1520+ * rb_shell_player_get_playing_entry:
1521+ * @player: the #RBShellPlayer
1522+ *
1523+ * Retrieves the currently playing #RhythmDBEntry, or NULL if
1524+ * nothing is playing. The caller must unref the entry
1525+ * (using #rhythmdb_entry_unref) when it is no longer needed.
1526+ *
1527+ * Return value: (transfer full) (allow-none): the currently playing #RhythmDBEntry, or NULL
1528+ */
1529+RhythmDBEntry *
1530+rb_shell_player_get_playing_entry (RBShellPlayer *player)
1531+{
1532+ RBPlayOrder *porder;
1533+ RhythmDBEntry *entry;
1534+
1535+ if (player->priv->current_playing_source == NULL) {
1536+ return NULL;
1537+ }
1538+
1539+ g_object_get (player->priv->current_playing_source, "play-order", &porder, NULL);
1540+ if (porder == NULL)
1541+ porder = g_object_ref (player->priv->play_order);
1542+
1543+ entry = rb_play_order_get_playing_entry (porder);
1544+ g_object_unref (porder);
1545+
1546+ return entry;
1547+}
1548+
1549+typedef struct {
1550+ RBShellPlayer *player;
1551+ char *location;
1552+ RhythmDBEntry *entry;
1553+ RBPlayerPlayType play_type;
1554+ GCancellable *cancellable;
1555+} OpenLocationThreadData;
1556+
1557+static void
1558+playlist_entry_cb (TotemPlParser *playlist,
1559+ const char *uri,
1560+ GHashTable *metadata,
1561+ OpenLocationThreadData *data)
1562+{
1563+ if (g_cancellable_is_cancelled (data->cancellable)) {
1564+ rb_debug ("playlist parser cancelled");
1565+ } else {
1566+ rb_debug ("adding stream url %s (%p)", uri, playlist);
1567+ g_queue_push_tail (data->player->priv->playlist_urls, g_strdup (uri));
1568+ }
1569+}
1570+
1571+static gpointer
1572+open_location_thread (OpenLocationThreadData *data)
1573+{
1574+ TotemPlParser *playlist;
1575+ TotemPlParserResult playlist_result;
1576+
1577+ playlist = totem_pl_parser_new ();
1578+
1579+ g_signal_connect_data (playlist, "entry-parsed",
1580+ G_CALLBACK (playlist_entry_cb),
1581+ data, NULL, 0);
1582+
1583+ totem_pl_parser_add_ignored_mimetype (playlist, "x-directory/normal");
1584+ totem_pl_parser_add_ignored_mimetype (playlist, "inode/directory");
1585+
1586+ playlist_result = totem_pl_parser_parse (playlist, data->location, FALSE);
1587+ g_object_unref (playlist);
1588+
1589+ if (g_cancellable_is_cancelled (data->cancellable)) {
1590+ playlist_result = TOTEM_PL_PARSER_RESULT_CANCELLED;
1591+ }
1592+
1593+ switch (playlist_result) {
1594+ case TOTEM_PL_PARSER_RESULT_SUCCESS:
1595+ if (g_queue_is_empty (data->player->priv->playlist_urls)) {
1596+ GError *error = g_error_new (RB_SHELL_PLAYER_ERROR,
1597+ RB_SHELL_PLAYER_ERROR_END_OF_PLAYLIST,
1598+ _("Playlist was empty"));
1599+ GDK_THREADS_ENTER ();
1600+ rb_shell_player_error (data->player, TRUE, error);
1601+ g_error_free (error);
1602+ GDK_THREADS_LEAVE ();
1603+ } else {
1604+ char *location;
1605+
1606+ location = g_queue_pop_head (data->player->priv->playlist_urls);
1607+ rb_debug ("playing first stream url %s", location);
1608+ rb_shell_player_open_playlist_url (data->player, location, data->entry, data->play_type);
1609+ g_free (location);
1610+ }
1611+ break;
1612+
1613+ case TOTEM_PL_PARSER_RESULT_CANCELLED:
1614+ rb_debug ("playlist parser was cancelled");
1615+ break;
1616+
1617+ default:
1618+ /* if we can't parse it as a playlist, just try playing it */
1619+ rb_debug ("playlist parser failed, playing %s directly", data->location);
1620+ rb_shell_player_open_playlist_url (data->player, data->location, data->entry, data->play_type);
1621+ break;
1622+ }
1623+
1624+ g_object_unref (data->cancellable);
1625+ g_free (data);
1626+ return NULL;
1627+}
1628+
1629+static gboolean
1630+rb_shell_player_open_location (RBShellPlayer *player,
1631+ RhythmDBEntry *entry,
1632+ RBPlayerPlayType play_type,
1633+ GError **error)
1634+{
1635+ char *location;
1636+ gboolean ret = TRUE;
1637+
1638+ /* dispose of any existing playlist urls */
1639+ if (player->priv->playlist_urls) {
1640+ g_queue_foreach (player->priv->playlist_urls,
1641+ (GFunc) g_free,
1642+ NULL);
1643+ g_queue_free (player->priv->playlist_urls);
1644+ player->priv->playlist_urls = NULL;
1645+ }
1646+ if (rb_source_try_playlist (player->priv->source)) {
1647+ player->priv->playlist_urls = g_queue_new ();
1648+ }
1649+
1650+ location = rhythmdb_entry_get_playback_uri (entry);
1651+ if (location == NULL) {
1652+ return FALSE;
1653+ }
1654+
1655+ if (rb_source_try_playlist (player->priv->source)) {
1656+ OpenLocationThreadData *data;
1657+
1658+ data = g_new0 (OpenLocationThreadData, 1);
1659+ data->player = player;
1660+ data->play_type = play_type;
1661+ data->entry = entry;
1662+
1663+ /* add http:// as a prefix, if it doesn't have a URI scheme */
1664+ if (strstr (location, "://"))
1665+ data->location = g_strdup (location);
1666+ else
1667+ data->location = g_strconcat ("http://", location, NULL);
1668+
1669+ if (player->priv->parser_cancellable == NULL) {
1670+ player->priv->parser_cancellable = g_cancellable_new ();
1671+ }
1672+ data->cancellable = g_object_ref (player->priv->parser_cancellable);
1673+
1674+ g_thread_create ((GThreadFunc)open_location_thread, data, FALSE, NULL);
1675+ } else {
1676+ if (player->priv->parser_cancellable != NULL) {
1677+ g_object_unref (player->priv->parser_cancellable);
1678+ player->priv->parser_cancellable = NULL;
1679+ }
1680+
1681+ rhythmdb_entry_ref (entry);
1682+ ret = ret && rb_player_open (player->priv->mmplayer, location, entry, (GDestroyNotify) rhythmdb_entry_unref, error);
1683+
1684+ ret = ret && rb_player_play (player->priv->mmplayer, play_type, player->priv->track_transition_time, error);
1685+ }
1686+
1687+ g_free (location);
1688+ return ret;
1689+}
1690+
1691+/**
1692+ * rb_shell_player_play:
1693+ * @player: a #RBShellPlayer
1694+ * @error: error return
1695+ *
1696+ * Starts playback, if it is not already playing.
1697+ *
1698+ * Return value: whether playback is now occurring (TRUE when successfully started
1699+ * or already playing).
1700+ **/
1701+gboolean
1702+rb_shell_player_play (RBShellPlayer *player,
1703+ GError **error)
1704+{
1705+ RBEntryView *songs;
1706+
1707+ if (player->priv->current_playing_source == NULL) {
1708+ rb_debug ("current playing source is NULL");
1709+ g_set_error (error,
1710+ RB_SHELL_PLAYER_ERROR,
1711+ RB_SHELL_PLAYER_ERROR_NOT_PLAYING,
1712+ "Current playing source is NULL");
1713+ return FALSE;
1714+ }
1715+
1716+ if (rb_player_playing (player->priv->mmplayer))
1717+ return TRUE;
1718+
1719+ if (player->priv->parser_cancellable != NULL) {
1720+ rb_debug ("currently parsing a playlist");
1721+ return TRUE;
1722+ }
1723+
1724+ /* we're obviously not playing anything, so crossfading is irrelevant */
1725+ if (!rb_player_play (player->priv->mmplayer, RB_PLAYER_PLAY_REPLACE, 0.0f, error)) {
1726+ rb_debug ("player doesn't want to");
1727+ return FALSE;
1728+ }
1729+
1730+ songs = rb_source_get_entry_view (player->priv->current_playing_source);
1731+ if (songs)
1732+ rb_entry_view_set_state (songs, RB_ENTRY_VIEW_PLAYING);
1733+
1734+ return TRUE;
1735+}
1736+
1737+static void
1738+rb_shell_player_set_entry_playback_error (RBShellPlayer *player,
1739+ RhythmDBEntry *entry,
1740+ char *message)
1741+{
1742+ GValue value = { 0, };
1743+
1744+ g_return_if_fail (RB_IS_SHELL_PLAYER (player));
1745+
1746+ g_value_init (&value, G_TYPE_STRING);
1747+ g_value_set_string (&value, message);
1748+ rhythmdb_entry_set (player->priv->db,
1749+ entry,
1750+ RHYTHMDB_PROP_PLAYBACK_ERROR,
1751+ &value);
1752+ g_value_unset (&value);
1753+ rhythmdb_commit (player->priv->db);
1754+}
1755+
1756+static gboolean
1757+rb_shell_player_set_playing_entry (RBShellPlayer *player,
1758+ RhythmDBEntry *entry,
1759+ gboolean out_of_order,
1760+ gboolean wait_for_eos,
1761+ GError **error)
1762+{
1763+ GError *tmp_error = NULL;
1764+ GValue val = {0,};
1765+ RBPlayerPlayType play_type;
1766+
1767+ g_return_val_if_fail (player->priv->current_playing_source != NULL, TRUE);
1768+ g_return_val_if_fail (entry != NULL, TRUE);
1769+
1770+ play_type = wait_for_eos ? RB_PLAYER_PLAY_AFTER_EOS : RB_PLAYER_PLAY_REPLACE;
1771+
1772+ if (out_of_order) {
1773+ RBPlayOrder *porder;
1774+
1775+ g_object_get (player->priv->current_playing_source, "play-order", &porder, NULL);
1776+ if (porder == NULL)
1777+ porder = g_object_ref (player->priv->play_order);
1778+ rb_play_order_set_playing_entry (porder, entry);
1779+ g_object_unref (porder);
1780+ }
1781+
1782+ if (player->priv->playing_entry != NULL &&
1783+ player->priv->track_transition_time > 0) {
1784+ const char *previous_album;
1785+ const char *album;
1786+
1787+ previous_album = rhythmdb_entry_get_string (player->priv->playing_entry, RHYTHMDB_PROP_ALBUM);
1788+ album = rhythmdb_entry_get_string (entry, RHYTHMDB_PROP_ALBUM);
1789+ /* only crossfade if we're not going from the end of one song on an
1790+ * album to the start of another. "Unknown" doesn't count as an album.
1791+ */
1792+ if (wait_for_eos == FALSE ||
1793+ strcmp (album, _("Unknown")) == 0 ||
1794+ strcmp (album, previous_album) != 0) {
1795+ play_type = RB_PLAYER_PLAY_CROSSFADE;
1796+ }
1797+ }
1798+
1799+ if (rb_shell_player_open_location (player, entry, play_type, &tmp_error) == FALSE) {
1800+ goto lose;
1801+ }
1802+
1803+ rb_debug ("Success!");
1804+ /* clear error on successful playback */
1805+ g_value_init (&val, G_TYPE_STRING);
1806+ g_value_set_string (&val, NULL);
1807+ rhythmdb_entry_set (player->priv->db, entry, RHYTHMDB_PROP_PLAYBACK_ERROR, &val);
1808+ rhythmdb_commit (player->priv->db);
1809+ g_value_unset (&val);
1810+
1811+ return TRUE;
1812+ lose:
1813+ /* Ignore errors, shutdown the player */
1814+ rb_player_close (player->priv->mmplayer, NULL /* XXX specify uri? */, NULL);
1815+
1816+ if (tmp_error == NULL) {
1817+ tmp_error = g_error_new (RB_SHELL_PLAYER_ERROR,
1818+ RB_SHELL_PLAYER_ERROR_NOT_PLAYING,
1819+ "Problem occurred without error being set. "
1820+ "This is a bug in Rhythmbox or GStreamer.");
1821+ }
1822+ /* Mark this song as failed */
1823+ rb_shell_player_set_entry_playback_error (player, entry, tmp_error->message);
1824+ g_propagate_error (error, tmp_error);
1825+
1826+ rb_shell_player_sync_with_source (player);
1827+ rb_shell_player_sync_buttons (player);
1828+ g_object_notify (G_OBJECT (player), "playing");
1829+
1830+ return FALSE;
1831+}
1832+
1833+static void
1834+player_settings_changed_cb (GSettings *settings, const char *key, RBShellPlayer *player)
1835+{
1836+ if (g_strcmp0 (key, "play-order") == 0) {
1837+ rb_debug ("play order setting changed");
1838+ player->priv->syncing_state = TRUE;
1839+ rb_shell_player_sync_play_order (player);
1840+ rb_shell_player_sync_buttons (player);
1841+ rb_shell_player_sync_control_state (player);
1842+ g_object_notify (G_OBJECT (player), "play-order");
1843+ player->priv->syncing_state = FALSE;
1844+ } else if (g_strcmp0 (key, "transition-time") == 0) {
1845+ double newtime;
1846+ rb_debug ("track transition time changed");
1847+ newtime = g_settings_get_double (player->priv->settings, "transition-time");
1848+ player->priv->track_transition_time = newtime * RB_PLAYER_SECOND;
1849+ }
1850+}
1851+
1852+/**
1853+ * rb_shell_player_get_playback_state:
1854+ * @player: the #RBShellPlayer
1855+ * @shuffle: (out): returns the current shuffle setting
1856+ * @repeat: (out): returns the current repeat setting
1857+ *
1858+ * Retrieves the current state of the shuffle and repeat settings.
1859+ *
1860+ * Return value: %TRUE if successful.
1861+ */
1862+gboolean
1863+rb_shell_player_get_playback_state (RBShellPlayer *player,
1864+ gboolean *shuffle,
1865+ gboolean *repeat)
1866+{
1867+ int i, j;
1868+ char *play_order;
1869+
1870+ play_order = g_settings_get_string (player->priv->settings, "play-order");
1871+ for (i = 0; i < G_N_ELEMENTS(state_to_play_order); i++)
1872+ for (j = 0; j < G_N_ELEMENTS(state_to_play_order[0]); j++)
1873+ if (!strcmp (play_order, state_to_play_order[i][j]))
1874+ goto found;
1875+
1876+ g_free (play_order);
1877+ return FALSE;
1878+
1879+found:
1880+ if (shuffle != NULL) {
1881+ *shuffle = i > 0;
1882+ }
1883+ if (repeat != NULL) {
1884+ *repeat = j > 0;
1885+ }
1886+ g_free (play_order);
1887+ return TRUE;
1888+}
1889+
1890+/**
1891+ * rb_shell_player_set_playback_state:
1892+ * @player: the #RBShellPlayer
1893+ * @shuffle: whether to enable the shuffle setting
1894+ * @repeat: whether to enable the repeat setting
1895+ *
1896+ * Sets the state of the shuffle and repeat settings.
1897+ */
1898+void
1899+rb_shell_player_set_playback_state (RBShellPlayer *player,
1900+ gboolean shuffle,
1901+ gboolean repeat)
1902+{
1903+ const char *neworder = state_to_play_order[shuffle ? 1 : 0][repeat ? 1 : 0];
1904+ g_settings_set_string (player->priv->settings, "play-order", neworder);
1905+}
1906+
1907+static void
1908+rb_shell_player_sync_play_order (RBShellPlayer *player)
1909+{
1910+ char *new_play_order;
1911+ RhythmDBEntry *playing_entry = NULL;
1912+ RBSource *source;
1913+
1914+ new_play_order = g_settings_get_string (player->priv->settings, "play-order");
1915+ if (player->priv->play_order != NULL) {
1916+ playing_entry = rb_play_order_get_playing_entry (player->priv->play_order);
1917+ g_signal_handlers_disconnect_by_func (player->priv->play_order,
1918+ G_CALLBACK (rb_shell_player_play_order_update_cb),
1919+ player);
1920+ g_object_unref (player->priv->play_order);
1921+ }
1922+
1923+ player->priv->play_order = rb_play_order_new (player, new_play_order);
1924+
1925+ g_signal_connect_object (player->priv->play_order,
1926+ "have_next_previous_changed",
1927+ G_CALLBACK (rb_shell_player_play_order_update_cb),
1928+ player, 0);
1929+ rb_shell_player_play_order_update_cb (player->priv->play_order,
1930+ FALSE, FALSE,
1931+ player);
1932+
1933+ source = player->priv->current_playing_source;
1934+ if (source == NULL) {
1935+ source = player->priv->selected_source;
1936+ }
1937+ rb_play_order_playing_source_changed (player->priv->play_order, source);
1938+
1939+ if (playing_entry != NULL) {
1940+ rb_play_order_set_playing_entry (player->priv->play_order, playing_entry);
1941+ rhythmdb_entry_unref (playing_entry);
1942+ }
1943+
1944+ g_free (new_play_order);
1945+}
1946+
1947+static void
1948+rb_shell_player_play_order_update_cb (RBPlayOrder *porder,
1949+ gboolean _has_next,
1950+ gboolean _has_previous,
1951+ RBShellPlayer *player)
1952+{
1953+ /* we cannot depend on the values of has_next, has_previous or porder
1954+ * since this can be called for the main porder, queue porder, etc
1955+ */
1956+ gboolean has_next = FALSE;
1957+ gboolean has_prev = FALSE;
1958+ RhythmDBEntry *entry;
1959+
1960+ entry = rb_shell_player_get_playing_entry (player);
1961+ if (entry != NULL) {
1962+ has_next = TRUE;
1963+ has_prev = TRUE;
1964+ rhythmdb_entry_unref (entry);
1965+ } else {
1966+ if (player->priv->current_playing_source &&
1967+ (rb_source_handle_eos (player->priv->current_playing_source) == RB_SOURCE_EOF_NEXT)) {
1968+ RBPlayOrder *porder;
1969+ g_object_get (player->priv->current_playing_source, "play-order", &porder, NULL);
1970+ if (porder == NULL)
1971+ porder = g_object_ref (player->priv->play_order);
1972+ has_next = rb_play_order_has_next (porder);
1973+ g_object_unref (porder);
1974+ }
1975+ if (player->priv->queue_play_order) {
1976+ has_next |= rb_play_order_has_next (player->priv->queue_play_order);
1977+ }
1978+ has_prev = (player->priv->current_playing_source != NULL);
1979+ }
1980+
1981+ if (has_prev != player->priv->has_prev) {
1982+ player->priv->has_prev = has_prev;
1983+ g_object_notify (G_OBJECT (player), "has-prev");
1984+ }
1985+ if (has_next != player->priv->has_next) {
1986+ player->priv->has_next = has_next;
1987+ g_object_notify (G_OBJECT (player), "has-next");
1988+ }
1989+}
1990+
1991+/**
1992+ * rb_shell_player_jump_to_current:
1993+ * @player: the #RBShellPlayer
1994+ *
1995+ * Scrolls the #RBEntryView for the current playing source so that
1996+ * the current playing entry is visible and selects the row for the
1997+ * entry. If there is no current playing entry, the selection is
1998+ * cleared instead.
1999+ */
2000+void
2001+rb_shell_player_jump_to_current (RBShellPlayer *player)
2002+{
2003+ RBSource *source;
2004+ RhythmDBEntry *entry;
2005+ RBEntryView *songs;
2006+
2007+ source = player->priv->current_playing_source ? player->priv->current_playing_source :
2008+ player->priv->selected_source;
2009+
2010+ songs = rb_source_get_entry_view (source);
2011+ entry = rb_shell_player_get_playing_entry (player);
2012+ if (songs != NULL) {
2013+ if (entry != NULL) {
2014+ rb_entry_view_scroll_to_entry (songs, entry);
2015+ rb_entry_view_select_entry (songs, entry);
2016+ } else {
2017+ rb_entry_view_select_none (songs);
2018+ }
2019+ }
2020+
2021+ if (entry != NULL) {
2022+ rhythmdb_entry_unref (entry);
2023+ }
2024+}
2025+
2026+static void
2027+swap_playing_source (RBShellPlayer *player,
2028+ RBSource *new_source)
2029+{
2030+ if (player->priv->current_playing_source != NULL) {
2031+ RBEntryView *old_songs = rb_source_get_entry_view (player->priv->current_playing_source);
2032+ if (old_songs)
2033+ rb_entry_view_set_state (old_songs, RB_ENTRY_VIEW_NOT_PLAYING);
2034+ }
2035+ if (new_source != NULL) {
2036+ RBEntryView *new_songs = rb_source_get_entry_view (new_source);
2037+
2038+ if (new_songs) {
2039+ rb_entry_view_set_state (new_songs, RB_ENTRY_VIEW_PLAYING);
2040+ rb_shell_player_set_playing_source (player, new_source);
2041+ }
2042+ }
2043+}
2044+
2045+/**
2046+ * rb_shell_player_do_previous:
2047+ * @player: the #RBShellPlayer
2048+ * @error: returns any error information
2049+ *
2050+ * If the current song has been playing for more than 3 seconds,
2051+ * restarts it, otherwise, goes back to the previous song.
2052+ * Fails if there is no current song, or if inside the first
2053+ * 3 seconds of the first song in the play order.
2054+ *
2055+ * Return value: %TRUE if successful
2056+ */
2057+gboolean
2058+rb_shell_player_do_previous (RBShellPlayer *player,
2059+ GError **error)
2060+{
2061+ RhythmDBEntry *entry = NULL;
2062+ RBSource *new_source;
2063+
2064+ if (player->priv->current_playing_source == NULL) {
2065+ g_set_error (error,
2066+ RB_SHELL_PLAYER_ERROR,
2067+ RB_SHELL_PLAYER_ERROR_NOT_PLAYING,
2068+ _("Not currently playing"));
2069+ return FALSE;
2070+ }
2071+
2072+ /* If we're in the first 3 seconds go to the previous song,
2073+ * else restart the current one.
2074+ */
2075+ if (player->priv->current_playing_source != NULL
2076+ && rb_source_can_pause (player->priv->source)
2077+ && rb_player_get_time (player->priv->mmplayer) > (G_GINT64_CONSTANT (3) * RB_PLAYER_SECOND)) {
2078+ rb_debug ("after 3 second previous, restarting song");
2079+ rb_player_set_time (player->priv->mmplayer, 0);
2080+ rb_shell_player_sync_with_source (player);
2081+ return TRUE;
2082+ }
2083+
2084+ rb_debug ("going to previous");
2085+
2086+ /* hrm, does this actually do anything at all? */
2087+ if (player->priv->queue_play_order) {
2088+ entry = rb_play_order_get_previous (player->priv->queue_play_order);
2089+ if (entry != NULL) {
2090+ new_source = RB_SOURCE (player->priv->queue_source);
2091+ rb_play_order_go_previous (player->priv->queue_play_order);
2092+ }
2093+ }
2094+
2095+ if (entry == NULL) {
2096+ RBPlayOrder *porder;
2097+
2098+ new_source = player->priv->source;
2099+ g_object_get (new_source, "play-order", &porder, NULL);
2100+ if (porder == NULL)
2101+ porder = g_object_ref (player->priv->play_order);
2102+
2103+ entry = rb_play_order_get_previous (porder);
2104+ if (entry)
2105+ rb_play_order_go_previous (porder);
2106+ g_object_unref (porder);
2107+ }
2108+
2109+ if (entry != NULL) {
2110+ rb_debug ("previous song found, doing previous");
2111+ if (new_source != player->priv->current_playing_source)
2112+ swap_playing_source (player, new_source);
2113+
2114+ player->priv->jump_to_playing_entry = TRUE;
2115+ if (!rb_shell_player_set_playing_entry (player, entry, FALSE, FALSE, error)) {
2116+ rhythmdb_entry_unref (entry);
2117+ return FALSE;
2118+ }
2119+
2120+ rhythmdb_entry_unref (entry);
2121+ } else {
2122+ rb_debug ("no previous song found, signalling error");
2123+ g_set_error (error,
2124+ RB_SHELL_PLAYER_ERROR,
2125+ RB_SHELL_PLAYER_ERROR_END_OF_PLAYLIST,
2126+ _("No previous song"));
2127+ rb_shell_player_stop (player);
2128+ return FALSE;
2129+ }
2130+
2131+ return TRUE;
2132+}
2133+
2134+static gboolean
2135+rb_shell_player_do_next_internal (RBShellPlayer *player, gboolean from_eos, gboolean allow_stop, GError **error)
2136+{
2137+ RBSource *new_source = NULL;
2138+ RhythmDBEntry *entry = NULL;
2139+ gboolean rv = TRUE;
2140+
2141+ if (player->priv->source == NULL)
2142+ return TRUE;
2143+
2144+
2145+ /* try the current playing source's play order, if it has one */
2146+ if (player->priv->current_playing_source != NULL) {
2147+ RBPlayOrder *porder;
2148+ g_object_get (player->priv->current_playing_source, "play-order", &porder, NULL);
2149+ if (porder != NULL) {
2150+ entry = rb_play_order_get_next (porder);
2151+ if (entry != NULL) {
2152+ rb_play_order_go_next (porder);
2153+ new_source = player->priv->current_playing_source;
2154+ }
2155+ g_object_unref (porder);
2156+ }
2157+ }
2158+
2159+ /* if that's different to the playing source that the user selected
2160+ * (ie we're playing from the queue), try that too
2161+ */
2162+ if (entry == NULL) {
2163+ RBPlayOrder *porder;
2164+ g_object_get (player->priv->source, "play-order", &porder, NULL);
2165+ if (porder == NULL)
2166+ porder = g_object_ref (player->priv->play_order);
2167+
2168+ /*
2169+ * If we interrupted this source to play from something else,
2170+ * we should go back to whatever it wanted to play before.
2171+ */
2172+ if (player->priv->source != player->priv->current_playing_source)
2173+ entry = rb_play_order_get_playing_entry (porder);
2174+
2175+ /* if that didn't help, advance the play order */
2176+ if (entry == NULL) {
2177+ entry = rb_play_order_get_next (porder);
2178+ if (entry != NULL) {
2179+ rb_debug ("got new entry %s from play order",
2180+ rhythmdb_entry_get_string (entry, RHYTHMDB_PROP_LOCATION));
2181+ rb_play_order_go_next (porder);
2182+ }
2183+ }
2184+
2185+ if (entry != NULL)
2186+ new_source = player->priv->source;
2187+
2188+ g_object_unref (porder);
2189+ }
2190+
2191+ /* if the new entry isn't from the play queue anyway, let the play queue
2192+ * override the regular play order.
2193+ */
2194+ if (player->priv->queue_play_order &&
2195+ new_source != RB_SOURCE (player->priv->queue_source)) {
2196+ RhythmDBEntry *queue_entry;
2197+
2198+ queue_entry = rb_play_order_get_next (player->priv->queue_play_order);
2199+ rb_play_order_go_next (player->priv->queue_play_order);
2200+ if (queue_entry != NULL) {
2201+ rb_debug ("got new entry %s from queue play order",
2202+ rhythmdb_entry_get_string (queue_entry, RHYTHMDB_PROP_LOCATION));
2203+ if (entry != NULL) {
2204+ rhythmdb_entry_unref (entry);
2205+ }
2206+ entry = queue_entry;
2207+ new_source = RB_SOURCE (player->priv->queue_source);
2208+ } else {
2209+ rb_debug ("didn't get a new entry from queue play order");
2210+ }
2211+ }
2212+
2213+ /* play the new entry */
2214+ if (entry != NULL) {
2215+ /* if the entry view containing the playing entry changed, update it */
2216+ if (new_source != player->priv->current_playing_source)
2217+ swap_playing_source (player, new_source);
2218+
2219+ player->priv->jump_to_playing_entry = TRUE;
2220+ if (!rb_shell_player_set_playing_entry (player, entry, FALSE, from_eos, error))
2221+ rv = FALSE;
2222+ } else {
2223+ g_set_error (error,
2224+ RB_SHELL_PLAYER_ERROR,
2225+ RB_SHELL_PLAYER_ERROR_END_OF_PLAYLIST,
2226+ _("No next song"));
2227+ rv = FALSE;
2228+
2229+ if (allow_stop) {
2230+ rb_debug ("No next entry, stopping playback");
2231+
2232+ /* hmm, need to set playing entry on the playing source's
2233+ * play order if it has one?
2234+ */
2235+
2236+ rb_shell_player_stop (player);
2237+ rb_play_order_set_playing_entry (player->priv->play_order, NULL);
2238+ }
2239+ }
2240+
2241+ if (entry != NULL) {
2242+ rhythmdb_entry_unref (entry);
2243+ }
2244+
2245+ return rv;
2246+}
2247+
2248+/**
2249+ * rb_shell_player_do_next:
2250+ * @player: the #RBShellPlayer
2251+ * @error: returns error information
2252+ *
2253+ * Skips to the next song. Consults the play queue and handles
2254+ * transitions between the play queue and the active source.
2255+ * Fails if there is no entry to play after the current one.
2256+ *
2257+ * Return value: %TRUE if successful
2258+ */
2259+gboolean
2260+rb_shell_player_do_next (RBShellPlayer *player,
2261+ GError **error)
2262+{
2263+ return rb_shell_player_do_next_internal (player, FALSE, TRUE, error);
2264+}
2265+
2266+static void
2267+rb_shell_player_cmd_previous (GtkAction *action,
2268+ RBShellPlayer *player)
2269+{
2270+ GError *error = NULL;
2271+
2272+ if (!rb_shell_player_do_previous (player, &error)) {
2273+ if (error->domain != RB_SHELL_PLAYER_ERROR ||
2274+ error->code != RB_SHELL_PLAYER_ERROR_END_OF_PLAYLIST) {
2275+ g_warning ("cmd_previous: Unhandled error: %s", error->message);
2276+ } else if (error->code == RB_SHELL_PLAYER_ERROR_END_OF_PLAYLIST) {
2277+ rb_shell_player_stop (player);
2278+ }
2279+ }
2280+}
2281+
2282+static void
2283+rb_shell_player_cmd_next (GtkAction *action,
2284+ RBShellPlayer *player)
2285+{
2286+ GError *error = NULL;
2287+
2288+ if (!rb_shell_player_do_next (player, &error)) {
2289+ if (error->domain != RB_SHELL_PLAYER_ERROR ||
2290+ error->code != RB_SHELL_PLAYER_ERROR_END_OF_PLAYLIST) {
2291+ g_warning ("cmd_next: Unhandled error: %s", error->message);
2292+ } else if (error->code == RB_SHELL_PLAYER_ERROR_END_OF_PLAYLIST) {
2293+ rb_shell_player_stop (player);
2294+ }
2295+ }
2296+}
2297+
2298+/**
2299+ * rb_shell_player_play_entry:
2300+ * @player: the #RBShellPlayer
2301+ * @entry: the #RhythmDBEntry to play
2302+ * @source: the new #RBSource to set as playing (or NULL to use the
2303+ * selected source)
2304+ *
2305+ * Plays a specified entry.
2306+ */
2307+void
2308+rb_shell_player_play_entry (RBShellPlayer *player,
2309+ RhythmDBEntry *entry,
2310+ RBSource *source)
2311+{
2312+ GError *error = NULL;
2313+
2314+ if (source == NULL)
2315+ source = player->priv->selected_source;
2316+ rb_shell_player_set_playing_source (player, source);
2317+
2318+ player->priv->jump_to_playing_entry = FALSE;
2319+ if (!rb_shell_player_set_playing_entry (player, entry, TRUE, FALSE, &error)) {
2320+ rb_shell_player_error (player, FALSE, error);
2321+ g_clear_error (&error);
2322+ }
2323+}
2324+
2325+static void
2326+rb_shell_player_cmd_volume_up (GtkAction *action,
2327+ RBShellPlayer *player)
2328+{
2329+ rb_shell_player_set_volume_relative (player, 0.1, NULL);
2330+}
2331+
2332+static void
2333+rb_shell_player_cmd_volume_down (GtkAction *action,
2334+ RBShellPlayer *player)
2335+{
2336+ rb_shell_player_set_volume_relative (player, -0.1, NULL);
2337+}
2338+
2339+static void
2340+rb_shell_player_cmd_play (GtkAction *action,
2341+ RBShellPlayer *player)
2342+{
2343+ GError *error = NULL;
2344+ rb_debug ("play!");
2345+ if (!rb_shell_player_playpause (player, FALSE, &error))
2346+ rb_error_dialog (NULL,
2347+ _("Couldn't start playback"),
2348+ "%s", (error) ? error->message : "(null)");
2349+ g_clear_error (&error);
2350+}
2351+
2352+/* unused parameter can't be removed without breaking dbus interface compatibility */
2353+/**
2354+ * rb_shell_player_playpause:
2355+ * @player: the #RBShellPlayer
2356+ * @unused: nothing
2357+ * @error: returns error information
2358+ *
2359+ * Toggles between playing and paused state. If there is no playing
2360+ * entry, chooses an entry from (in order of preference) the play queue,
2361+ * the selection in the current source, or the play order.
2362+ *
2363+ * Return value: %TRUE if successful
2364+ */
2365+gboolean
2366+rb_shell_player_playpause (RBShellPlayer *player,
2367+ gboolean unused,
2368+ GError **error)
2369+{
2370+ gboolean ret;
2371+ RBEntryView *songs;
2372+
2373+ rb_debug ("doing playpause");
2374+
2375+ g_return_val_if_fail (RB_IS_SHELL_PLAYER (player), TRUE);
2376+
2377+ ret = TRUE;
2378+
2379+ if (rb_player_playing (player->priv->mmplayer)) {
2380+ if (player->priv->source == NULL) {
2381+ rb_debug ("playing source is already NULL");
2382+ } else if (rb_source_can_pause (player->priv->source)) {
2383+ rb_debug ("pausing mm player");
2384+ rb_player_pause (player->priv->mmplayer);
2385+ songs = rb_source_get_entry_view (player->priv->current_playing_source);
2386+ if (songs)
2387+ rb_entry_view_set_state (songs, RB_ENTRY_VIEW_PAUSED);
2388+
2389+ /* might need a signal for when the player has actually paused here? */
2390+ g_object_notify (G_OBJECT (player), "playing");
2391+ /* mostly for that */
2392+ } else {
2393+ rb_debug ("stopping playback");
2394+ rb_shell_player_stop (player);
2395+ }
2396+ } else {
2397+ RhythmDBEntry *entry;
2398+ RBSource *new_source;
2399+ gboolean out_of_order = FALSE;
2400+
2401+ if (player->priv->source == NULL) {
2402+ /* no current stream, pull one in from the currently
2403+ * selected source */
2404+ rb_debug ("no playing source, using selected source");
2405+ rb_shell_player_set_playing_source (player, player->priv->selected_source);
2406+ }
2407+ new_source = player->priv->current_playing_source;
2408+
2409+ entry = rb_shell_player_get_playing_entry (player);
2410+ if (entry == NULL) {
2411+ /* queue takes precedence over selection */
2412+ if (player->priv->queue_play_order) {
2413+ entry = rb_play_order_get_next (player->priv->queue_play_order);
2414+ if (entry != NULL) {
2415+ new_source = RB_SOURCE (player->priv->queue_source);
2416+ rb_play_order_go_next (player->priv->queue_play_order);
2417+ }
2418+ }
2419+
2420+ /* selection takes precedence over first item in play order */
2421+ if (entry == NULL) {
2422+ GList *selection = NULL;
2423+
2424+ songs = rb_source_get_entry_view (player->priv->source);
2425+ if (songs)
2426+ selection = rb_entry_view_get_selected_entries (songs);
2427+
2428+ if (selection != NULL) {
2429+ rb_debug ("choosing first selected entry");
2430+ entry = (RhythmDBEntry*) selection->data;
2431+ if (entry)
2432+ out_of_order = TRUE;
2433+
2434+ g_list_free (selection);
2435+ }
2436+ }
2437+
2438+ /* play order is last */
2439+ if (entry == NULL) {
2440+ RBPlayOrder *porder;
2441+
2442+ rb_debug ("getting entry from play order");
2443+ g_object_get (player->priv->source, "play-order", &porder, NULL);
2444+ if (porder == NULL)
2445+ porder = g_object_ref (player->priv->play_order);
2446+
2447+ entry = rb_play_order_get_next (porder);
2448+ if (entry != NULL)
2449+ rb_play_order_go_next (porder);
2450+ g_object_unref (porder);
2451+ }
2452+
2453+ if (entry != NULL) {
2454+ /* if the entry view containing the playing entry changed, update it */
2455+ if (new_source != player->priv->current_playing_source)
2456+ swap_playing_source (player, new_source);
2457+
2458+ player->priv->jump_to_playing_entry = TRUE;
2459+ if (!rb_shell_player_set_playing_entry (player, entry, out_of_order, FALSE, error))
2460+ ret = FALSE;
2461+ }
2462+ } else {
2463+ if (!rb_shell_player_play (player, error)) {
2464+ rb_shell_player_stop (player);
2465+ ret = FALSE;
2466+ }
2467+ }
2468+
2469+ if (entry != NULL) {
2470+ rhythmdb_entry_unref (entry);
2471+ }
2472+ }
2473+
2474+ rb_shell_player_sync_with_source (player);
2475+ rb_shell_player_sync_buttons (player);
2476+
2477+ return ret;
2478+}
2479+
2480+static void
2481+rb_shell_player_sync_control_state (RBShellPlayer *player)
2482+{
2483+ gboolean shuffle, repeat;
2484+ GtkAction *action;
2485+ rb_debug ("syncing control state");
2486+
2487+ if (!rb_shell_player_get_playback_state (player, &shuffle,
2488+ &repeat))
2489+ return;
2490+
2491+ action = gtk_action_group_get_action (player->priv->actiongroup,
2492+ "ControlShuffle");
2493+ gtk_toggle_action_set_active (GTK_TOGGLE_ACTION (action), shuffle);
2494+ action = gtk_action_group_get_action (player->priv->actiongroup,
2495+ "ControlRepeat");
2496+ gtk_toggle_action_set_active (GTK_TOGGLE_ACTION (action), repeat);
2497+}
2498+
2499+static void
2500+sync_volume_cb (GSettings *settings, RBShellPlayer *player)
2501+{
2502+ g_settings_set_double (player->priv->settings, "volume", player->priv->volume);
2503+}
2504+
2505+static void
2506+rb_shell_player_sync_volume (RBShellPlayer *player,
2507+ gboolean notify,
2508+ gboolean set_volume)
2509+{
2510+ GtkAction *action;
2511+ RhythmDBEntry *entry;
2512+
2513+ if (player->priv->volume <= 0.0){
2514+ player->priv->volume = 0.0;
2515+ } else if (player->priv->volume >= 1.0){
2516+ player->priv->volume = 1.0;
2517+ }
2518+
2519+ action = gtk_action_group_get_action (player->priv->actiongroup,
2520+ "ControlVolumeUp");
2521+ g_object_set (G_OBJECT (action), "sensitive", player->priv->volume < 0.9999, NULL);
2522+
2523+ action = gtk_action_group_get_action (player->priv->actiongroup,
2524+ "ControlVolumeDown");
2525+ g_object_set (G_OBJECT (action), "sensitive", player->priv->volume > 0.0001, NULL);
2526+
2527+ if (set_volume) {
2528+ rb_player_set_volume (player->priv->mmplayer,
2529+ player->priv->mute ? 0.0 : player->priv->volume);
2530+ }
2531+
2532+ if (player->priv->syncing_state == FALSE) {
2533+ rb_settings_delayed_sync (player->priv->settings,
2534+ (RBDelayedSyncFunc) sync_volume_cb,
2535+ g_object_ref (player),
2536+ g_object_unref);
2537+ }
2538+
2539+ entry = rb_shell_player_get_playing_entry (player);
2540+ if (entry != NULL) {
2541+ rhythmdb_entry_unref (entry);
2542+ }
2543+
2544+ if (notify)
2545+ g_object_notify (G_OBJECT (player), "volume");
2546+}
2547+
2548+/**
2549+ * rb_shell_player_set_volume:
2550+ * @player: the #RBShellPlayer
2551+ * @volume: the volume level (between 0 and 1)
2552+ * @error: returns the error information
2553+ *
2554+ * Sets the playback volume level.
2555+ *
2556+ * Return value: %TRUE on success
2557+ */
2558+gboolean
2559+rb_shell_player_set_volume (RBShellPlayer *player,
2560+ gdouble volume,
2561+ GError **error)
2562+{
2563+ player->priv->volume = volume;
2564+ rb_shell_player_sync_volume (player, TRUE, TRUE);
2565+ return TRUE;
2566+}
2567+
2568+/**
2569+ * rb_shell_player_set_volume_relative:
2570+ * @player: the #RBShellPlayer
2571+ * @delta: difference to apply to the volume level (between -1 and 1)
2572+ * @error: returns error information
2573+ *
2574+ * Adds the specified value to the current volume level.
2575+ *
2576+ * Return value: %TRUE on success
2577+ */
2578+gboolean
2579+rb_shell_player_set_volume_relative (RBShellPlayer *player,
2580+ gdouble delta,
2581+ GError **error)
2582+{
2583+ /* rb_shell_player_sync_volume does clipping */
2584+ player->priv->volume += delta;
2585+ rb_shell_player_sync_volume (player, TRUE, TRUE);
2586+ return TRUE;
2587+}
2588+
2589+/**
2590+ * rb_shell_player_get_volume:
2591+ * @player: the #RBShellPlayer
2592+ * @volume: (out): returns the volume level
2593+ * @error: returns error information
2594+ *
2595+ * Returns the current volume level
2596+ *
2597+ * Return value: the current volume level.
2598+ */
2599+gboolean
2600+rb_shell_player_get_volume (RBShellPlayer *player,
2601+ gdouble *volume,
2602+ GError **error)
2603+{
2604+ *volume = player->priv->volume;
2605+ return TRUE;
2606+}
2607+
2608+static void
2609+rb_shell_player_volume_changed_cb (RBPlayer *player,
2610+ float volume,
2611+ RBShellPlayer *shell_player)
2612+{
2613+ shell_player->priv->volume = volume;
2614+ rb_shell_player_sync_volume (shell_player, TRUE, FALSE);
2615+}
2616+
2617+/**
2618+ * rb_shell_player_set_mute
2619+ * @player: the #RBShellPlayer
2620+ * @mute: %TRUE to mute playback
2621+ * @error: returns error information
2622+ *
2623+ * Updates the mute setting on the player.
2624+ *
2625+ * Return value: %TRUE if successful
2626+ */
2627+gboolean
2628+rb_shell_player_set_mute (RBShellPlayer *player,
2629+ gboolean mute,
2630+ GError **error)
2631+{
2632+ player->priv->mute = mute;
2633+ rb_shell_player_sync_volume (player, FALSE, TRUE);
2634+ return TRUE;
2635+}
2636+
2637+/**
2638+ * rb_shell_player_get_mute:
2639+ * @player: the #RBShellPlayer
2640+ * @mute: (out): returns the current mute setting
2641+ * @error: returns error information
2642+ *
2643+ * Returns %TRUE if currently muted
2644+ *
2645+ * Return value: %TRUE if currently muted
2646+ */
2647+gboolean
2648+rb_shell_player_get_mute (RBShellPlayer *player,
2649+ gboolean *mute,
2650+ GError **error)
2651+{
2652+ *mute = player->priv->mute;
2653+ return TRUE;
2654+}
2655+
2656+static void
2657+rb_shell_player_shuffle_changed_cb (GtkAction *action,
2658+ RBShellPlayer *player)
2659+{
2660+ const char *neworder;
2661+ gboolean shuffle = FALSE;
2662+ gboolean repeat = FALSE;
2663+
2664+ if (player->priv->syncing_state)
2665+ return;
2666+
2667+ rb_debug ("shuffle changed");
2668+
2669+ rb_shell_player_get_playback_state (player, &shuffle, &repeat);
2670+
2671+ shuffle = !shuffle;
2672+ neworder = state_to_play_order[shuffle ? 1 : 0][repeat ? 1 : 0];
2673+ g_settings_set_string (player->priv->settings, "play-order", neworder);
2674+}
2675+
2676+static void
2677+rb_shell_player_repeat_changed_cb (GtkAction *action,
2678+ RBShellPlayer *player)
2679+{
2680+ const char *neworder;
2681+ gboolean shuffle = FALSE;
2682+ gboolean repeat = FALSE;
2683+ rb_debug ("repeat changed");
2684+
2685+ if (player->priv->syncing_state)
2686+ return;
2687+
2688+ rb_shell_player_get_playback_state (player, &shuffle, &repeat);
2689+
2690+ repeat = !repeat;
2691+ neworder = state_to_play_order[shuffle ? 1 : 0][repeat ? 1 : 0];
2692+ g_settings_set_string (player->priv->settings, "play-order", neworder);
2693+}
2694+
2695+static void
2696+rb_shell_player_entry_activated_cb (RBEntryView *view,
2697+ RhythmDBEntry *entry,
2698+ RBShellPlayer *player)
2699+{
2700+ gboolean was_from_queue = FALSE;
2701+ RhythmDBEntry *prev_entry = NULL;
2702+ GError *error = NULL;
2703+ gboolean source_set = FALSE;
2704+ gboolean jump_to_entry = FALSE;
2705+ char *playback_uri;
2706+
2707+ g_return_if_fail (entry != NULL);
2708+
2709+ rb_debug ("got entry %p activated", entry);
2710+
2711+ /* don't play hidden entries */
2712+ if (rhythmdb_entry_get_boolean (entry, RHYTHMDB_PROP_HIDDEN))
2713+ return;
2714+
2715+ /* skip entries with no playback uri */
2716+ playback_uri = rhythmdb_entry_get_playback_uri (entry);
2717+ if (playback_uri == NULL)
2718+ return;
2719+
2720+ g_free (playback_uri);
2721+
2722+ /* figure out where the previous entry came from */
2723+ if ((player->priv->queue_source != NULL) &&
2724+ (player->priv->current_playing_source == RB_SOURCE (player->priv->queue_source))) {
2725+ prev_entry = rb_shell_player_get_playing_entry (player);
2726+ was_from_queue = TRUE;
2727+ }
2728+
2729+ if (player->priv->queue_source) {
2730+ RBEntryView *queue_sidebar;
2731+
2732+ g_object_get (player->priv->queue_source, "sidebar", &queue_sidebar, NULL);
2733+
2734+ if (view == queue_sidebar || view == rb_source_get_entry_view (RB_SOURCE (player->priv->queue_source))) {
2735+
2736+ /* fall back to the current selected source once the queue is empty */
2737+ if (view == queue_sidebar && player->priv->source == NULL) {
2738+ /* XXX only do this if the selected source doesn't have its own play order? */
2739+ rb_play_order_playing_source_changed (player->priv->play_order,
2740+ player->priv->selected_source);
2741+ player->priv->source = player->priv->selected_source;
2742+ }
2743+
2744+ rb_shell_player_set_playing_source (player, RB_SOURCE (player->priv->queue_source));
2745+
2746+ was_from_queue = FALSE;
2747+ source_set = TRUE;
2748+ jump_to_entry = TRUE;
2749+ } else {
2750+ if (player->priv->queue_only) {
2751+ rb_source_add_to_queue (player->priv->selected_source,
2752+ RB_SOURCE (player->priv->queue_source));
2753+ rb_shell_player_set_playing_source (player, RB_SOURCE (player->priv->queue_source));
2754+ source_set = TRUE;
2755+ }
2756+ }
2757+
2758+ g_object_unref (queue_sidebar);
2759+ }
2760+
2761+ /* bail out if queue only */
2762+ if (player->priv->queue_only) {
2763+ return;
2764+ }
2765+
2766+ if (!source_set) {
2767+ rb_shell_player_set_playing_source (player, player->priv->selected_source);
2768+ source_set = TRUE;
2769+ }
2770+
2771+ player->priv->jump_to_playing_entry = jump_to_entry;
2772+ if (!rb_shell_player_set_playing_entry (player, entry, TRUE, FALSE, &error)) {
2773+ rb_shell_player_error (player, FALSE, error);
2774+ g_clear_error (&error);
2775+ }
2776+
2777+ /* if we were previously playing from the queue, clear its playing entry,
2778+ * so we'll start again from the start.
2779+ */
2780+ if (was_from_queue && prev_entry != NULL) {
2781+ rb_play_order_set_playing_entry (player->priv->queue_play_order, NULL);
2782+ }
2783+
2784+ if (prev_entry != NULL) {
2785+ rhythmdb_entry_unref (prev_entry);
2786+ }
2787+}
2788+
2789+static void
2790+rb_shell_player_property_row_activated_cb (RBPropertyView *view,
2791+ const char *name,
2792+ RBShellPlayer *player)
2793+{
2794+ RBPlayOrder *porder;
2795+ RhythmDBEntry *entry = NULL;
2796+ GError *error = NULL;
2797+
2798+ rb_debug ("got property activated");
2799+
2800+ rb_shell_player_set_playing_source (player, player->priv->selected_source);
2801+
2802+ /* RHYTHMDBFIXME - do we need to wait here until the query is finished?
2803+ * in theory, yes, but in practice the query is started when the row is
2804+ * selected (on the first click when doubleclicking, or when using the
2805+ * keyboard to select then activate) and is pretty much always done by
2806+ * the time we get in here.
2807+ */
2808+
2809+ g_object_get (player->priv->selected_source, "play-order", &porder, NULL);
2810+ if (porder == NULL)
2811+ porder = g_object_ref (player->priv->play_order);
2812+
2813+ entry = rb_play_order_get_next (porder);
2814+ if (entry != NULL) {
2815+ rb_play_order_go_next (porder);
2816+
2817+ player->priv->jump_to_playing_entry = TRUE; /* ? */
2818+ if (!rb_shell_player_set_playing_entry (player, entry, TRUE, FALSE, &error)) {
2819+ rb_shell_player_error (player, FALSE, error);
2820+ g_clear_error (&error);
2821+ }
2822+ }
2823+
2824+ rhythmdb_entry_unref (entry);
2825+ g_object_unref (porder);
2826+}
2827+
2828+static void
2829+rb_shell_player_entry_changed_cb (RhythmDB *db,
2830+ RhythmDBEntry *entry,
2831+ GValueArray *changes,
2832+ RBShellPlayer *player)
2833+{
2834+ gboolean synced = FALSE;
2835+ const char *location;
2836+ RhythmDBEntry *playing_entry;
2837+ int i;
2838+
2839+ playing_entry = rb_shell_player_get_playing_entry (player);
2840+
2841+ /* We try to update only if the changed entry is currently playing */
2842+ if (entry != playing_entry) {
2843+ if (playing_entry != NULL) {
2844+ rhythmdb_entry_unref (playing_entry);
2845+ }
2846+ return;
2847+ }
2848+
2849+ location = rhythmdb_entry_get_string (entry, RHYTHMDB_PROP_LOCATION);
2850+ for (i = 0; i < changes->n_values; i++) {
2851+ GValue *v = g_value_array_get_nth (changes, i);
2852+ RhythmDBEntryChange *change = g_value_get_boxed (v);
2853+
2854+ /* update UI if the artist, title or album has changed */
2855+ switch (change->prop) {
2856+ case RHYTHMDB_PROP_TITLE:
2857+ case RHYTHMDB_PROP_ARTIST:
2858+ case RHYTHMDB_PROP_ALBUM:
2859+ if (!synced) {
2860+ rb_shell_player_sync_with_source (player);
2861+ synced = TRUE;
2862+ }
2863+ break;
2864+ default:
2865+ break;
2866+ }
2867+
2868+ /* emit dbus signals for changes with easily marshallable types */
2869+ switch (rhythmdb_get_property_type (db, change->prop)) {
2870+ case G_TYPE_STRING:
2871+ case G_TYPE_BOOLEAN:
2872+ case G_TYPE_ULONG:
2873+ case G_TYPE_UINT64:
2874+ case G_TYPE_DOUBLE:
2875+ g_signal_emit (G_OBJECT (player),
2876+ rb_shell_player_signals[PLAYING_SONG_PROPERTY_CHANGED], 0,
2877+ location,
2878+ rhythmdb_nice_elt_name_from_propid (db, change->prop),
2879+ &change->old,
2880+ &change->new);
2881+ break;
2882+ default:
2883+ break;
2884+ }
2885+ }
2886+
2887+ if (playing_entry != NULL) {
2888+ rhythmdb_entry_unref (playing_entry);
2889+ }
2890+}
2891+
2892+static void
2893+rb_shell_player_extra_metadata_cb (RhythmDB *db,
2894+ RhythmDBEntry *entry,
2895+ const char *field,
2896+ GValue *metadata,
2897+ RBShellPlayer *player)
2898+{
2899+
2900+ RhythmDBEntry *playing_entry;
2901+
2902+ playing_entry = rb_shell_player_get_playing_entry (player);
2903+ if (entry != playing_entry) {
2904+ if (playing_entry != NULL) {
2905+ rhythmdb_entry_unref (playing_entry);
2906+ }
2907+ return;
2908+ }
2909+
2910+ rb_shell_player_sync_with_source (player);
2911+
2912+ /* emit dbus signals for changes with easily marshallable types */
2913+ switch (G_VALUE_TYPE (metadata)) {
2914+ case G_TYPE_STRING:
2915+ /* make sure it's valid utf8, otherwise dbus barfs */
2916+ if (g_utf8_validate (g_value_get_string (metadata), -1, NULL) == FALSE) {
2917+ rb_debug ("not emitting extra metadata field %s as value is not valid utf8", field);
2918+ return;
2919+ }
2920+ case G_TYPE_BOOLEAN:
2921+ case G_TYPE_ULONG:
2922+ case G_TYPE_UINT64:
2923+ case G_TYPE_DOUBLE:
2924+ g_signal_emit (G_OBJECT (player),
2925+ rb_shell_player_signals[PLAYING_SONG_PROPERTY_CHANGED], 0,
2926+ rhythmdb_entry_get_string (entry, RHYTHMDB_PROP_LOCATION),
2927+ field,
2928+ metadata, /* slightly silly */
2929+ metadata);
2930+ break;
2931+ default:
2932+ break;
2933+ }
2934+}
2935+
2936+
2937+static void
2938+rb_shell_player_sync_with_source (RBShellPlayer *player)
2939+{
2940+ const char *entry_title = NULL;
2941+ const char *artist = NULL;
2942+ const char *stream_name = NULL;
2943+ char *streaming_title = NULL;
2944+ char *streaming_artist = NULL;
2945+ RhythmDBEntry *entry;
2946+ char *title = NULL;
2947+ gint64 elapsed;
2948+
2949+ entry = rb_shell_player_get_playing_entry (player);
2950+ rb_debug ("playing source: %p, active entry: %p", player->priv->current_playing_source, entry);
2951+
2952+ if (entry != NULL) {
2953+ GValue *value;
2954+
2955+ entry_title = rhythmdb_entry_get_string (entry, RHYTHMDB_PROP_TITLE);
2956+ artist = rhythmdb_entry_get_string (entry, RHYTHMDB_PROP_ARTIST);
2957+
2958+ value = rhythmdb_entry_request_extra_metadata (player->priv->db,
2959+ entry,
2960+ RHYTHMDB_PROP_STREAM_SONG_TITLE);
2961+ if (value != NULL) {
2962+ streaming_title = g_value_dup_string (value);
2963+ g_value_unset (value);
2964+ g_free (value);
2965+
2966+ rb_debug ("got streaming title \"%s\"", streaming_title);
2967+ /* use entry title for stream name */
2968+ stream_name = entry_title;
2969+ entry_title = streaming_title;
2970+ }
2971+
2972+ value = rhythmdb_entry_request_extra_metadata (player->priv->db,
2973+ entry,
2974+ RHYTHMDB_PROP_STREAM_SONG_ARTIST);
2975+ if (value != NULL) {
2976+ streaming_artist = g_value_dup_string (value);
2977+ g_value_unset (value);
2978+ g_free (value);
2979+
2980+ rb_debug ("got streaming artist \"%s\"", streaming_artist);
2981+ /* override artist from entry */
2982+ artist = streaming_artist;
2983+ }
2984+
2985+ rhythmdb_entry_unref (entry);
2986+ }
2987+
2988+ if ((artist && artist[0] != '\0') || entry_title || stream_name) {
2989+
2990+ GString *title_str = g_string_sized_new (100);
2991+ if (artist && artist[0] != '\0') {
2992+ g_string_append (title_str, artist);
2993+ g_string_append (title_str, " - ");
2994+ }
2995+ if (entry_title != NULL)
2996+ g_string_append (title_str, entry_title);
2997+
2998+ if (stream_name != NULL)
2999+ g_string_append_printf (title_str, " (%s)", stream_name);
3000+
3001+ title = g_string_free (title_str, FALSE);
3002+ }
3003+
3004+ elapsed = rb_player_get_time (player->priv->mmplayer);
3005+ if (elapsed < 0)
3006+ elapsed = 0;
3007+ player->priv->elapsed = elapsed / RB_PLAYER_SECOND;
3008+
3009+ g_signal_emit (G_OBJECT (player), rb_shell_player_signals[WINDOW_TITLE_CHANGED], 0,
3010+ title);
3011+ g_free (title);
3012+
3013+ g_signal_emit (G_OBJECT (player), rb_shell_player_signals[ELAPSED_CHANGED], 0,
3014+ player->priv->elapsed);
3015+
3016+ g_free (streaming_artist);
3017+ g_free (streaming_title);
3018+}
3019+
3020+static void
3021+rb_shell_player_sync_buttons (RBShellPlayer *player)
3022+{
3023+ GtkAction *action;
3024+ RBSource *source;
3025+ RBEntryView *view;
3026+ int entry_view_state;
3027+ RhythmDBEntry *entry;
3028+
3029+ entry = rb_shell_player_get_playing_entry (player);
3030+ if (entry != NULL) {
3031+ source = player->priv->current_playing_source;
3032+ entry_view_state = rb_player_playing (player->priv->mmplayer) ?
3033+ RB_ENTRY_VIEW_PLAYING : RB_ENTRY_VIEW_PAUSED;
3034+ } else {
3035+ source = player->priv->selected_source;
3036+ entry_view_state = RB_ENTRY_VIEW_NOT_PLAYING;
3037+ }
3038+
3039+ source = (entry == NULL) ? player->priv->selected_source : player->priv->current_playing_source;
3040+
3041+ rb_debug ("syncing with source %p", source);
3042+
3043+ action = gtk_action_group_get_action (player->priv->actiongroup,
3044+ "ViewJumpToPlaying");
3045+ g_object_set (action, "sensitive", entry != NULL, NULL);
3046+
3047+ action = gtk_action_group_get_action (player->priv->actiongroup,
3048+ "ControlPlay");
3049+ g_object_set (action, "sensitive", entry != NULL || source != NULL, NULL);
3050+
3051+ if (source != NULL) {
3052+ view = rb_source_get_entry_view (source);
3053+ if (view)
3054+ rb_entry_view_set_state (view, entry_view_state);
3055+ }
3056+
3057+ if (entry != NULL) {
3058+ rhythmdb_entry_unref (entry);
3059+ }
3060+}
3061+
3062+/**
3063+ * rb_shell_player_set_playing_source:
3064+ * @player: the #RBShellPlayer
3065+ * @source: the new playing #RBSource
3066+ *
3067+ * Replaces the current playing source.
3068+ */
3069+void
3070+rb_shell_player_set_playing_source (RBShellPlayer *player,
3071+ RBSource *source)
3072+{
3073+ rb_shell_player_set_playing_source_internal (player, source, TRUE);
3074+}
3075+
3076+static void
3077+actually_set_playing_source (RBShellPlayer *player,
3078+ RBSource *source,
3079+ gboolean sync_entry_view)
3080+{
3081+ RBPlayOrder *porder;
3082+
3083+ player->priv->source = source;
3084+ player->priv->current_playing_source = source;
3085+
3086+ if (source != NULL) {
3087+ RBEntryView *songs = rb_source_get_entry_view (player->priv->source);
3088+ if (sync_entry_view && songs) {
3089+ rb_entry_view_set_state (songs, RB_ENTRY_VIEW_PLAYING);
3090+ }
3091+ }
3092+
3093+ if (source != RB_SOURCE (player->priv->queue_source)) {
3094+ if (source == NULL)
3095+ source = player->priv->selected_source;
3096+
3097+ if (source != NULL) {
3098+ g_object_get (source, "play-order", &porder, NULL);
3099+ if (porder == NULL)
3100+ porder = g_object_ref (player->priv->play_order);
3101+
3102+ rb_play_order_playing_source_changed (porder, source);
3103+ g_object_unref (porder);
3104+ }
3105+ }
3106+
3107+ rb_shell_player_play_order_update_cb (player->priv->play_order,
3108+ FALSE, FALSE,
3109+ player);
3110+}
3111+
3112+static void
3113+rb_shell_player_set_playing_source_internal (RBShellPlayer *player,
3114+ RBSource *source,
3115+ gboolean sync_entry_view)
3116+
3117+{
3118+ gboolean emit_source_changed = TRUE;
3119+ gboolean emit_playing_from_queue_changed = FALSE;
3120+
3121+ if (player->priv->source == source &&
3122+ player->priv->current_playing_source == source &&
3123+ source != NULL)
3124+ return;
3125+
3126+ rb_debug ("setting playing source to %p", source);
3127+
3128+ if (RB_SOURCE (player->priv->queue_source) == source) {
3129+
3130+ if (player->priv->current_playing_source != source)
3131+ emit_playing_from_queue_changed = TRUE;
3132+
3133+ if (player->priv->source == NULL) {
3134+ actually_set_playing_source (player, source, sync_entry_view);
3135+ } else {
3136+ emit_source_changed = FALSE;
3137+ player->priv->current_playing_source = source;
3138+ }
3139+
3140+ } else {
3141+ if (player->priv->current_playing_source != source) {
3142+ if (player->priv->current_playing_source == RB_SOURCE (player->priv->queue_source))
3143+ emit_playing_from_queue_changed = TRUE;
3144+
3145+ /* stop the old source */
3146+ if (player->priv->current_playing_source != NULL) {
3147+ if (sync_entry_view) {
3148+ RBEntryView *songs = rb_source_get_entry_view (player->priv->current_playing_source);
3149+ rb_debug ("source is already playing, stopping it");
3150+
3151+ /* clear the playing entry if we're switching between non-queue sources */
3152+ if (player->priv->current_playing_source != RB_SOURCE (player->priv->queue_source))
3153+ rb_play_order_set_playing_entry (player->priv->play_order, NULL);
3154+
3155+ if (songs)
3156+ rb_entry_view_set_state (songs, RB_ENTRY_VIEW_NOT_PLAYING);
3157+ }
3158+ }
3159+ }
3160+ actually_set_playing_source (player, source, sync_entry_view);
3161+ }
3162+
3163+ rb_shell_player_sync_with_source (player);
3164+ /*g_object_notify (G_OBJECT (player), "playing");*/
3165+ if (player->priv->selected_source)
3166+ rb_shell_player_sync_buttons (player);
3167+
3168+ if (emit_source_changed) {
3169+ g_signal_emit (G_OBJECT (player), rb_shell_player_signals[PLAYING_SOURCE_CHANGED],
3170+ 0, player->priv->source);
3171+ }
3172+ if (emit_playing_from_queue_changed) {
3173+ g_object_notify (G_OBJECT (player), "playing-from-queue");
3174+ }
3175+}
3176+
3177+/**
3178+ * rb_shell_player_stop:
3179+ * @player: a #RBShellPlayer.
3180+ *
3181+ * Completely stops playback, freeing resources and unloading the file.
3182+ *
3183+ * In general rb_shell_player_pause() should be used instead, as it stops the
3184+ * audio, but does not completely free resources.
3185+ **/
3186+void
3187+rb_shell_player_stop (RBShellPlayer *player)
3188+{
3189+ GError *error = NULL;
3190+ rb_debug ("stopping");
3191+
3192+ g_return_if_fail (RB_IS_SHELL_PLAYER (player));
3193+
3194+ if (error == NULL)
3195+ rb_player_close (player->priv->mmplayer, NULL, &error);
3196+ if (error) {
3197+ rb_error_dialog (NULL,
3198+ _("Couldn't stop playback"),
3199+ "%s", error->message);
3200+ g_error_free (error);
3201+ }
3202+
3203+ if (player->priv->parser_cancellable != NULL) {
3204+ rb_debug ("cancelling playlist parser");
3205+ g_cancellable_cancel (player->priv->parser_cancellable);
3206+ g_object_unref (player->priv->parser_cancellable);
3207+ player->priv->parser_cancellable = NULL;
3208+ }
3209+
3210+ if (player->priv->playing_entry != NULL) {
3211+ rhythmdb_entry_unref (player->priv->playing_entry);
3212+ player->priv->playing_entry = NULL;
3213+ }
3214+
3215+ rb_shell_player_set_playing_source (player, NULL);
3216+ rb_shell_player_sync_with_source (player);
3217+ g_signal_emit (G_OBJECT (player),
3218+ rb_shell_player_signals[PLAYING_SONG_CHANGED], 0,
3219+ NULL);
3220+ g_signal_emit (G_OBJECT (player),
3221+ rb_shell_player_signals[PLAYING_URI_CHANGED], 0,
3222+ NULL);
3223+ g_object_notify (G_OBJECT (player), "playing");
3224+ rb_shell_player_sync_buttons (player);
3225+}
3226+
3227+/**
3228+ * rb_shell_player_pause:
3229+ * @player: a #RBShellPlayer
3230+ * @error: error return
3231+ *
3232+ * Pauses playback if possible, completely stopping if not.
3233+ *
3234+ * Return value: whether playback is not occurring (TRUE when successfully
3235+ * paused/stopped or playback was not occurring).
3236+ **/
3237+
3238+gboolean
3239+rb_shell_player_pause (RBShellPlayer *player,
3240+ GError **error)
3241+{
3242+ if (rb_player_playing (player->priv->mmplayer))
3243+ return rb_shell_player_playpause (player, FALSE, error);
3244+ else
3245+ return TRUE;
3246+}
3247+
3248+/**
3249+ * rb_shell_player_get_playing:
3250+ * @player: a #RBShellPlayer
3251+ * @playing: (out): playback state return
3252+ * @error: error return
3253+ *
3254+ * Reports whether playback is occuring by setting #playing.
3255+ *
3256+ * Return value: %TRUE if successful
3257+ **/
3258+gboolean
3259+rb_shell_player_get_playing (RBShellPlayer *player,
3260+ gboolean *playing,
3261+ GError **error)
3262+{
3263+ if (playing != NULL)
3264+ *playing = rb_player_playing (player->priv->mmplayer);
3265+
3266+ return TRUE;
3267+}
3268+
3269+/**
3270+ * rb_shell_player_get_playing_time_string:
3271+ * @player: the #RBShellPlayer
3272+ *
3273+ * Constructs a string showing the current playback position,
3274+ * taking the time display settings into account.
3275+ *
3276+ * Return value: allocated playing time string
3277+ */
3278+char *
3279+rb_shell_player_get_playing_time_string (RBShellPlayer *player)
3280+{
3281+ gboolean elapsed;
3282+ elapsed = g_settings_get_boolean (player->priv->ui_settings, "time-display");
3283+ return rb_make_elapsed_time_string (player->priv->elapsed,
3284+ rb_shell_player_get_playing_song_duration (player),
3285+ elapsed);
3286+}
3287+
3288+/**
3289+ * rb_shell_player_get_playing_time:
3290+ * @player: the #RBShellPlayer
3291+ * @time: (out): returns the current playback position
3292+ * @error: returns error information
3293+ *
3294+ * Retrieves the current playback position. Fails if
3295+ * the player currently cannot provide the playback
3296+ * position.
3297+ *
3298+ * Return value: %TRUE if successful
3299+ */
3300+gboolean
3301+rb_shell_player_get_playing_time (RBShellPlayer *player,
3302+ guint *time,
3303+ GError **error)
3304+{
3305+ gint64 ptime;
3306+
3307+ ptime = rb_player_get_time (player->priv->mmplayer);
3308+ if (ptime >= 0) {
3309+ if (time != NULL) {
3310+ *time = (guint)(ptime / RB_PLAYER_SECOND);
3311+ }
3312+ return TRUE;
3313+ } else {
3314+ g_set_error (error,
3315+ RB_SHELL_PLAYER_ERROR,
3316+ RB_SHELL_PLAYER_ERROR_POSITION_NOT_AVAILABLE,
3317+ _("Playback position not available"));
3318+ return FALSE;
3319+ }
3320+}
3321+
3322+/**
3323+ * rb_shell_player_set_playing_time:
3324+ * @player: the #RBShellPlayer
3325+ * @time: the target playback position (in seconds)
3326+ * @error: returns error information
3327+ *
3328+ * Attempts to set the playback position. Fails if the
3329+ * current song is not seekable.
3330+ *
3331+ * Return value: %TRUE if successful
3332+ */
3333+gboolean
3334+rb_shell_player_set_playing_time (RBShellPlayer *player,
3335+ guint time,
3336+ GError **error)
3337+{
3338+ if (rb_player_seekable (player->priv->mmplayer)) {
3339+ if (player->priv->playing_entry_eos) {
3340+ rb_debug ("forgetting that playing entry had EOS'd due to seek");
3341+ player->priv->playing_entry_eos = FALSE;
3342+ }
3343+ rb_player_set_time (player->priv->mmplayer, ((gint64) time) * RB_PLAYER_SECOND);
3344+ return TRUE;
3345+ } else {
3346+ g_set_error (error,
3347+ RB_SHELL_PLAYER_ERROR,
3348+ RB_SHELL_PLAYER_ERROR_NOT_SEEKABLE,
3349+ _("Current song is not seekable"));
3350+ return FALSE;
3351+ }
3352+}
3353+
3354+/**
3355+ * rb_shell_player_seek:
3356+ * @player: the #RBShellPlayer
3357+ * @offset: relative seek target (in seconds)
3358+ * @error: returns error information
3359+ *
3360+ * Seeks forwards or backwards in the current playing
3361+ * song. Fails if the current song is not seekable.
3362+ *
3363+ * Return value: %TRUE if successful
3364+ */
3365+gboolean
3366+rb_shell_player_seek (RBShellPlayer *player,
3367+ gint32 offset,
3368+ GError **error)
3369+{
3370+ g_return_val_if_fail (RB_IS_SHELL_PLAYER (player), FALSE);
3371+
3372+ if (rb_player_seekable (player->priv->mmplayer)) {
3373+ gint64 target_time = rb_player_get_time (player->priv->mmplayer) +
3374+ (((gint64)offset) * RB_PLAYER_SECOND);
3375+ if (target_time < 0)
3376+ target_time = 0;
3377+ rb_player_set_time (player->priv->mmplayer, target_time);
3378+ return TRUE;
3379+ } else {
3380+ g_set_error (error,
3381+ RB_SHELL_PLAYER_ERROR,
3382+ RB_SHELL_PLAYER_ERROR_NOT_SEEKABLE,
3383+ _("Current song is not seekable"));
3384+ return FALSE;
3385+ }
3386+}
3387+
3388+/**
3389+ * rb_shell_player_get_playing_song_duration:
3390+ * @player: the #RBShellPlayer
3391+ *
3392+ * Retrieves the duration of the current playing song.
3393+ *
3394+ * Return value: duration, or -1 if not playing
3395+ */
3396+long
3397+rb_shell_player_get_playing_song_duration (RBShellPlayer *player)
3398+{
3399+ RhythmDBEntry *current_entry;
3400+ long val;
3401+
3402+ g_return_val_if_fail (RB_IS_SHELL_PLAYER (player), -1);
3403+
3404+ current_entry = rb_shell_player_get_playing_entry (player);
3405+
3406+ if (current_entry == NULL) {
3407+ rb_debug ("Did not get playing entry : return -1 as length");
3408+ return -1;
3409+ }
3410+
3411+ val = rhythmdb_entry_get_ulong (current_entry, RHYTHMDB_PROP_DURATION);
3412+
3413+ rhythmdb_entry_unref (current_entry);
3414+
3415+ return val;
3416+}
3417+
3418+static void
3419+rb_shell_player_sync_with_selected_source (RBShellPlayer *player)
3420+{
3421+ rb_debug ("syncing with selected source: %p", player->priv->selected_source);
3422+ if (player->priv->source == NULL)
3423+ {
3424+ rb_debug ("no playing source, new source is %p", player->priv->selected_source);
3425+ rb_shell_player_sync_with_source (player);
3426+ }
3427+}
3428+
3429+static gboolean
3430+do_next_idle (RBShellPlayer *player)
3431+{
3432+ /* use the EOS callback, so that EOF_SOURCE_ conditions are handled properly */
3433+ rb_shell_player_handle_eos (NULL, NULL, FALSE, player);
3434+ player->priv->do_next_idle_id = 0;
3435+
3436+ return FALSE;
3437+}
3438+
3439+static gboolean
3440+do_next_not_found_idle (RBShellPlayer *player)
3441+{
3442+ RhythmDBEntry *entry;
3443+ entry = rb_shell_player_get_playing_entry (player);
3444+
3445+ do_next_idle (player);
3446+
3447+ if (entry != NULL) {
3448+ rhythmdb_entry_update_availability (entry, RHYTHMDB_ENTRY_AVAIL_NOT_FOUND);
3449+ rhythmdb_commit (player->priv->db);
3450+ rhythmdb_entry_unref (entry);
3451+ }
3452+
3453+ return FALSE;
3454+}
3455+
3456+static void
3457+rb_shell_player_error (RBShellPlayer *player,
3458+ gboolean async,
3459+ const GError *err)
3460+{
3461+ RhythmDBEntry *entry;
3462+ gboolean do_next;
3463+
3464+ g_return_if_fail (player->priv->handling_error == FALSE);
3465+
3466+ player->priv->handling_error = TRUE;
3467+
3468+ entry = rb_shell_player_get_playing_entry (player);
3469+
3470+ rb_debug ("playback error while playing: %s", err->message);
3471+ /* For synchronous errors the entry playback error has already been set */
3472+ if (entry && async)
3473+ rb_shell_player_set_entry_playback_error (player, entry, err->message);
3474+
3475+ if (entry == NULL) {
3476+ do_next = TRUE;
3477+ } else if (err->domain == RB_PLAYER_ERROR && err->code == RB_PLAYER_ERROR_NOT_FOUND) {
3478+ /* process not found errors after we've started the next track */
3479+ if (player->priv->do_next_idle_id != 0) {
3480+ g_source_remove (player->priv->do_next_idle_id);
3481+ }
3482+ player->priv->do_next_idle_id = g_idle_add ((GSourceFunc)do_next_not_found_idle, player);
3483+ do_next = FALSE;
3484+ } else if (err->domain == RB_PLAYER_ERROR && err->code == RB_PLAYER_ERROR_NO_AUDIO) {
3485+
3486+ /* stream has completely ended */
3487+ rb_shell_player_stop (player);
3488+ do_next = FALSE;
3489+ } else if ((player->priv->current_playing_source != NULL) &&
3490+ (rb_source_handle_eos (player->priv->current_playing_source) == RB_SOURCE_EOF_RETRY)) {
3491+ /* receiving an error means a broken stream or non-audio stream, so abort
3492+ * unless we've got more URLs to try */
3493+ if (g_queue_is_empty (player->priv->playlist_urls)) {
3494+ rb_error_dialog (NULL,
3495+ _("Couldn't start playback"),
3496+ "%s", (err) ? err->message : "(null)");
3497+ rb_shell_player_stop (player);
3498+ do_next = FALSE;
3499+ } else {
3500+ rb_debug ("haven't yet exhausted the URLs from the playlist");
3501+ do_next = TRUE;
3502+ }
3503+ } else {
3504+ do_next = TRUE;
3505+ }
3506+
3507+ if (do_next && player->priv->do_next_idle_id == 0) {
3508+ player->priv->do_next_idle_id = g_idle_add ((GSourceFunc)do_next_idle, player);
3509+ }
3510+
3511+ player->priv->handling_error = FALSE;
3512+
3513+ if (entry != NULL) {
3514+ rhythmdb_entry_unref (entry);
3515+ }
3516+}
3517+
3518+static void
3519+playing_stream_cb (RBPlayer *mmplayer,
3520+ RhythmDBEntry *entry,
3521+ RBShellPlayer *player)
3522+{
3523+ gboolean entry_changed;
3524+
3525+ g_return_if_fail (entry != NULL);
3526+
3527+ GDK_THREADS_ENTER ();
3528+
3529+ entry_changed = (player->priv->playing_entry != entry);
3530+
3531+ /* update playing entry */
3532+ if (player->priv->playing_entry)
3533+ rhythmdb_entry_unref (player->priv->playing_entry);
3534+ player->priv->playing_entry = rhythmdb_entry_ref (entry);
3535+ player->priv->playing_entry_eos = FALSE;
3536+
3537+ if (entry_changed) {
3538+ const char *location;
3539+
3540+ location = rhythmdb_entry_get_string (entry, RHYTHMDB_PROP_LOCATION);
3541+ rb_debug ("new playing stream: %s", location);
3542+ g_signal_emit (G_OBJECT (player),
3543+ rb_shell_player_signals[PLAYING_SONG_CHANGED], 0,
3544+ entry);
3545+ g_signal_emit (G_OBJECT (player),
3546+ rb_shell_player_signals[PLAYING_URI_CHANGED], 0,
3547+ location);
3548+ }
3549+
3550+ /* resync UI */
3551+ rb_shell_player_sync_with_source (player);
3552+ rb_shell_player_sync_buttons (player);
3553+ g_object_notify (G_OBJECT (player), "playing");
3554+
3555+ if (player->priv->jump_to_playing_entry) {
3556+ rb_shell_player_jump_to_current (player);
3557+ player->priv->jump_to_playing_entry = FALSE;
3558+ }
3559+
3560+ GDK_THREADS_LEAVE ();
3561+}
3562+
3563+static void
3564+error_cb (RBPlayer *mmplayer,
3565+ RhythmDBEntry *entry,
3566+ const GError *err,
3567+ gpointer data)
3568+{
3569+ RBShellPlayer *player = RB_SHELL_PLAYER (data);
3570+
3571+ if (player->priv->handling_error)
3572+ return;
3573+
3574+ if (player->priv->source == NULL) {
3575+ rb_debug ("ignoring error (no source): %s", err->message);
3576+ return;
3577+ }
3578+
3579+ GDK_THREADS_ENTER ();
3580+
3581+ if (entry != player->priv->playing_entry) {
3582+ rb_debug ("got error for unexpected entry %p (expected %p)", entry, player->priv->playing_entry);
3583+ } else {
3584+ rb_shell_player_error (player, TRUE, err);
3585+ rb_debug ("exiting error hander");
3586+ }
3587+
3588+ GDK_THREADS_LEAVE ();
3589+}
3590+
3591+static void
3592+tick_cb (RBPlayer *mmplayer,
3593+ RhythmDBEntry *entry,
3594+ gint64 elapsed,
3595+ gint64 duration,
3596+ gpointer data)
3597+{
3598+ RBShellPlayer *player = RB_SHELL_PLAYER (data);
3599+ gint64 remaining_check = 0;
3600+ gboolean duration_from_player = TRUE;
3601+ const char *uri;
3602+ long elapsed_sec;
3603+
3604+ GDK_THREADS_ENTER ();
3605+
3606+ if (player->priv->playing_entry != entry) {
3607+ rb_debug ("got tick for unexpected entry %p (expected %p)", entry, player->priv->playing_entry);
3608+ GDK_THREADS_LEAVE ();
3609+ return;
3610+ }
3611+
3612+ /* if we aren't getting a duration value from the player, use the
3613+ * value from the entry, if any.
3614+ */
3615+ if (duration < 1) {
3616+ duration = ((gint64)rhythmdb_entry_get_ulong (entry, RHYTHMDB_PROP_DURATION)) * RB_PLAYER_SECOND;
3617+ duration_from_player = FALSE;
3618+ }
3619+
3620+ uri = rhythmdb_entry_get_string (entry, RHYTHMDB_PROP_LOCATION);
3621+ rb_debug ("tick: [%s, %" G_GINT64_FORMAT ":%" G_GINT64_FORMAT "(%d)]",
3622+ uri,
3623+ elapsed,
3624+ duration,
3625+ duration_from_player);
3626+
3627+ if (elapsed < 0) {
3628+ elapsed_sec = 0;
3629+ } else {
3630+ elapsed_sec = elapsed / RB_PLAYER_SECOND;
3631+ }
3632+
3633+ if (player->priv->elapsed != elapsed_sec) {
3634+ player->priv->elapsed = elapsed_sec;
3635+ g_signal_emit (G_OBJECT (player), rb_shell_player_signals[ELAPSED_CHANGED],
3636+ 0, player->priv->elapsed);
3637+ }
3638+ g_signal_emit (player, rb_shell_player_signals[ELAPSED_NANO_CHANGED], 0, elapsed);
3639+
3640+ if (duration_from_player) {
3641+ /* XXX update duration in various things? */
3642+ }
3643+
3644+ /* check if we should start a crossfade */
3645+ if (rb_player_multiple_open (mmplayer)) {
3646+ if (player->priv->track_transition_time < PREROLL_TIME) {
3647+ remaining_check = PREROLL_TIME;
3648+ } else {
3649+ remaining_check = player->priv->track_transition_time;
3650+ }
3651+ }
3652+
3653+ /*
3654+ * just pretending we got an EOS will do exactly what we want
3655+ * here. if we don't want to crossfade, we'll just leave the stream
3656+ * prerolled until the current stream really ends.
3657+ */
3658+ if (remaining_check > 0 &&
3659+ duration > 0 &&
3660+ elapsed > 0 &&
3661+ ((duration - elapsed) <= remaining_check)) {
3662+ rb_debug ("%" G_GINT64_FORMAT " ns remaining in stream %s; need %" G_GINT64_FORMAT " for transition",
3663+ duration - elapsed,
3664+ uri,
3665+ remaining_check);
3666+ rb_shell_player_handle_eos_unlocked (player, entry, FALSE);
3667+ }
3668+
3669+ GDK_THREADS_LEAVE ();
3670+}
3671+
3672+typedef struct {
3673+ RhythmDBEntry *entry;
3674+ RBShellPlayer *player;
3675+} MissingPluginRetryData;
3676+
3677+static void
3678+missing_plugins_retry_cb (gpointer inst,
3679+ gboolean retry,
3680+ MissingPluginRetryData *retry_data)
3681+{
3682+ GError *error = NULL;
3683+ if (retry == FALSE) {
3684+ /* next? or stop playback? */
3685+ rb_debug ("not retrying playback; stopping player");
3686+ rb_shell_player_stop (retry_data->player);
3687+ return;
3688+ }
3689+
3690+ rb_debug ("retrying playback");
3691+ rb_shell_player_set_playing_entry (retry_data->player,
3692+ retry_data->entry,
3693+ FALSE, FALSE,
3694+ &error);
3695+ if (error != NULL) {
3696+ rb_shell_player_error (retry_data->player, FALSE, error);
3697+ g_clear_error (&error);
3698+ }
3699+}
3700+
3701+static void
3702+missing_plugins_retry_cleanup (MissingPluginRetryData *retry)
3703+{
3704+ retry->player->priv->handling_error = FALSE;
3705+
3706+ g_object_unref (retry->player);
3707+ rhythmdb_entry_unref (retry->entry);
3708+ g_free (retry);
3709+}
3710+
3711+
3712+static void
3713+missing_plugins_cb (RBPlayer *player,
3714+ RhythmDBEntry *entry,
3715+ const char **details,
3716+ const char **descriptions,
3717+ RBShellPlayer *sp)
3718+{
3719+ gboolean processing;
3720+ GClosure *retry;
3721+ MissingPluginRetryData *retry_data;
3722+
3723+ retry_data = g_new0 (MissingPluginRetryData, 1);
3724+ retry_data->player = g_object_ref (sp);
3725+ retry_data->entry = rhythmdb_entry_ref (entry);
3726+
3727+ retry = g_cclosure_new ((GCallback) missing_plugins_retry_cb,
3728+ retry_data,
3729+ (GClosureNotify) missing_plugins_retry_cleanup);
3730+ g_closure_set_marshal (retry, g_cclosure_marshal_VOID__BOOLEAN);
3731+ processing = rb_missing_plugins_install (details, FALSE, retry);
3732+ if (processing) {
3733+ /* don't handle any further errors */
3734+ sp->priv->handling_error = TRUE;
3735+
3736+ /* probably specify the URI here.. */
3737+ rb_debug ("stopping player while processing missing plugins");
3738+ rb_player_close (retry_data->player->priv->mmplayer, NULL, NULL);
3739+ } else {
3740+ rb_debug ("not processing missing plugins; simulating EOS");
3741+ rb_shell_player_handle_eos (NULL, NULL, FALSE, retry_data->player);
3742+ }
3743+
3744+ g_closure_sink (retry);
3745+}
3746+
3747+static void
3748+player_image_cb (RBPlayer *player,
3749+ RhythmDBEntry *entry,
3750+ GdkPixbuf *image,
3751+ RBShellPlayer *shell_player)
3752+{
3753+ RBExtDB *store;
3754+ RBExtDBKey *key;
3755+ const char *artist;
3756+ GValue v = G_VALUE_INIT;
3757+
3758+ if (image == NULL)
3759+ return;
3760+
3761+ artist = rhythmdb_entry_get_string (entry, RHYTHMDB_PROP_ALBUM_ARTIST);
3762+ if (artist == NULL || artist[0] == '\0' || strcmp (artist, _("Unknown")) == 0) {
3763+ artist = rhythmdb_entry_get_string (entry, RHYTHMDB_PROP_ARTIST);
3764+ if (artist == NULL || artist[0] == '\0' || strcmp (artist, _("Unknown")) == 0) {
3765+ return;
3766+ }
3767+ }
3768+
3769+ store = rb_ext_db_new ("album-art");
3770+
3771+ key = rb_ext_db_key_create_storage ("album", rhythmdb_entry_get_string (entry, RHYTHMDB_PROP_ALBUM));
3772+ rb_ext_db_key_add_field (key, "artist", artist);
3773+
3774+ g_value_init (&v, GDK_TYPE_PIXBUF);
3775+ g_value_set_object (&v, image);
3776+ rb_ext_db_store (store, key, RB_EXT_DB_SOURCE_EMBEDDED, &v);
3777+ g_value_unset (&v);
3778+
3779+ g_object_unref (store);
3780+ rb_ext_db_key_free (key);
3781+}
3782+
3783+/**
3784+ * rb_shell_player_get_playing_path:
3785+ * @player: the #RBShellPlayer
3786+ * @path: (out callee-allocates) (transfer full): returns the URI of the current playing entry
3787+ * @error: returns error information
3788+ *
3789+ * Retrieves the URI of the current playing entry. The
3790+ * caller must not free the returned string.
3791+ *
3792+ * Return value: %TRUE if successful
3793+ */
3794+gboolean
3795+rb_shell_player_get_playing_path (RBShellPlayer *player,
3796+ const gchar **path,
3797+ GError **error)
3798+{
3799+ RhythmDBEntry *entry;
3800+
3801+ entry = rb_shell_player_get_playing_entry (player);
3802+ if (entry != NULL) {
3803+ *path = rhythmdb_entry_get_string (entry, RHYTHMDB_PROP_LOCATION);
3804+ } else {
3805+ *path = NULL;
3806+ }
3807+
3808+ if (entry != NULL) {
3809+ rhythmdb_entry_unref (entry);
3810+ }
3811+
3812+ return TRUE;
3813+}
3814+
3815+static gboolean
3816+_idle_unblock_signal_cb (gpointer data)
3817+{
3818+ RBShellPlayer *player = (RBShellPlayer *)data;
3819+ GtkAction *action;
3820+ gboolean playing;
3821+
3822+ GDK_THREADS_ENTER ();
3823+
3824+ player->priv->unblock_play_id = 0;
3825+
3826+ action = gtk_action_group_get_action (player->priv->actiongroup,
3827+ "ControlPlay");
3828+
3829+ /* sync the active state of the action again */
3830+ g_object_get (player, "playing", &playing, NULL);
3831+
3832+ g_signal_handlers_unblock_by_func (action, rb_shell_player_cmd_play, player);
3833+
3834+ GDK_THREADS_LEAVE ();
3835+ return FALSE;
3836+}
3837+
3838+static void
3839+rb_shell_player_playing_changed_cb (RBShellPlayer *player,
3840+ GParamSpec *arg1,
3841+ gpointer user_data)
3842+{
3843+ GtkAction *action;
3844+ gboolean playing;
3845+ char *tooltip;
3846+
3847+ g_object_get (player, "playing", &playing, NULL);
3848+ action = gtk_action_group_get_action (player->priv->actiongroup,
3849+ "ControlPlay");
3850+ if (playing) {
3851+ if (rb_source_can_pause (player->priv->source)) {
3852+ tooltip = g_strdup (_("Pause playback"));
3853+ gtk_action_set_stock_id (action, GTK_STOCK_MEDIA_PAUSE);
3854+ gtk_action_set_label (action, _("_Pause"));
3855+ } else {
3856+ tooltip = g_strdup (_("Stop playback"));
3857+ gtk_action_set_stock_id (action, GTK_STOCK_MEDIA_STOP);
3858+ gtk_action_set_label (action, _("_Stop"));
3859+ }
3860+ } else {
3861+ tooltip = g_strdup (_("Start playback"));
3862+ gtk_action_set_stock_id (action, GTK_STOCK_MEDIA_PLAY);
3863+ gtk_action_set_label (action, _("_Play"));
3864+ }
3865+ g_object_set (action, "tooltip", tooltip, NULL);
3866+ g_free (tooltip);
3867+
3868+ /* block the signal, so that it doesn't get stuck by triggering recursively,
3869+ * and don't unblock it until whatever else is happening has finished.
3870+ * don't block it again if it's already blocked, though.
3871+ */
3872+ if (player->priv->unblock_play_id == 0) {
3873+ g_signal_handlers_block_by_func (action, rb_shell_player_cmd_play, player);
3874+ }
3875+
3876+ if (player->priv->unblock_play_id == 0) {
3877+ player->priv->unblock_play_id = g_idle_add (_idle_unblock_signal_cb, player);
3878+ }
3879+}
3880+
3881+/* This should really be standard. */
3882+#define ENUM_ENTRY(NAME, DESC) { NAME, "" #NAME "", DESC }
3883+
3884+GType
3885+rb_shell_player_error_get_type (void)
3886+{
3887+ static GType etype = 0;
3888+
3889+ if (etype == 0) {
3890+ static const GEnumValue values[] = {
3891+ ENUM_ENTRY (RB_SHELL_PLAYER_ERROR_PLAYLIST_PARSE_ERROR, "playlist-parse-failed"),
3892+ ENUM_ENTRY (RB_SHELL_PLAYER_ERROR_END_OF_PLAYLIST, "end-of-playlist"),
3893+ ENUM_ENTRY (RB_SHELL_PLAYER_ERROR_NOT_PLAYING, "not-playing"),
3894+ ENUM_ENTRY (RB_SHELL_PLAYER_ERROR_NOT_SEEKABLE, "not-seekable"),
3895+ ENUM_ENTRY (RB_SHELL_PLAYER_ERROR_POSITION_NOT_AVAILABLE, "position-not-available"),
3896+ { 0, 0, 0 }
3897+ };
3898+
3899+ etype = g_enum_register_static ("RBShellPlayerError", values);
3900+ }
3901+
3902+ return etype;
3903+}
3904+
3905+static void
3906+_play_order_description_free (RBPlayOrderDescription *order)
3907+{
3908+ g_free (order->name);
3909+ g_free (order->description);
3910+ g_free (order);
3911+}
3912+
3913+/**
3914+ * rb_play_order_new:
3915+ * @porder_name: Play order type name
3916+ * @player: #RBShellPlayer instance to attach to
3917+ *
3918+ * Creates a new #RBPlayOrder of the specified type.
3919+ *
3920+ * Returns: #RBPlayOrder instance
3921+ **/
3922+
3923+#define DEFAULT_PLAY_ORDER "linear"
3924+
3925+static RBPlayOrder *
3926+rb_play_order_new (RBShellPlayer *player, const char* porder_name)
3927+{
3928+ RBPlayOrderDescription *order;
3929+
3930+ g_return_val_if_fail (porder_name != NULL, NULL);
3931+ g_return_val_if_fail (player != NULL, NULL);
3932+
3933+ order = g_hash_table_lookup (player->priv->play_orders, porder_name);
3934+
3935+ if (order == NULL) {
3936+ g_warning ("Unknown value \"%s\" in GSettings key \"play-order"
3937+ "\". Using %s play order.", porder_name, DEFAULT_PLAY_ORDER);
3938+ order = g_hash_table_lookup (player->priv->play_orders, DEFAULT_PLAY_ORDER);
3939+ }
3940+
3941+ return RB_PLAY_ORDER (g_object_new (order->order_type, "player", player, NULL));
3942+}
3943+
3944+/**
3945+ * rb_shell_player_add_play_order:
3946+ * @player: the #RBShellPlayer
3947+ * @name: name of the new play order
3948+ * @description: description of the new play order
3949+ * @order_type: the #GType of the play order class
3950+ * @hidden: if %TRUE, don't display the play order in the UI
3951+ *
3952+ * Adds a new play order to the set of available play orders.
3953+ */
3954+void
3955+rb_shell_player_add_play_order (RBShellPlayer *player, const char *name,
3956+ const char *description, GType order_type, gboolean hidden)
3957+{
3958+ RBPlayOrderDescription *order;
3959+
3960+ g_return_if_fail (g_type_is_a (order_type, RB_TYPE_PLAY_ORDER));
3961+
3962+ order = g_new0(RBPlayOrderDescription, 1);
3963+ order->name = g_strdup (name);
3964+ order->description = g_strdup (description);
3965+ order->order_type = order_type;
3966+ order->is_in_dropdown = !hidden;
3967+
3968+ g_hash_table_insert (player->priv->play_orders, order->name, order);
3969+}
3970+
3971+/**
3972+ * rb_shell_player_remove_play_order:
3973+ * @player: the #RBShellPlayer
3974+ * @name: name of the play order to remove
3975+ *
3976+ * Removes a play order previously added with #rb_shell_player_add_play_order
3977+ * from the set of available play orders.
3978+ */
3979+void
3980+rb_shell_player_remove_play_order (RBShellPlayer *player, const char *name)
3981+{
3982+ g_hash_table_remove (player->priv->play_orders, name);
3983+}
3984+
3985
3986=== modified file '.pc/applied-patches'
3987--- .pc/applied-patches 2012-09-19 14:30:03 +0000
3988+++ .pc/applied-patches 2012-11-07 20:44:28 +0000
3989@@ -7,6 +7,8 @@
3990 08_CVE-2012-3355.patch
3991 09_keywords.patch
3992 10_encoding_use_ubuntu_profiles.patch
3993+11_fix_cd_pausing.patch
3994 dont_free_consumed_floating_gvariant.patch
3995 git_scale_click.patch
3996 git_crash_during_monitor.patch
3997+git_rhythmdb_fix_deadlock.patch
3998
3999=== added directory '.pc/git_rhythmdb_fix_deadlock.patch'
4000=== added directory '.pc/git_rhythmdb_fix_deadlock.patch/rhythmdb'
4001=== added file '.pc/git_rhythmdb_fix_deadlock.patch/rhythmdb/rhythmdb.c'
4002--- .pc/git_rhythmdb_fix_deadlock.patch/rhythmdb/rhythmdb.c 1970-01-01 00:00:00 +0000
4003+++ .pc/git_rhythmdb_fix_deadlock.patch/rhythmdb/rhythmdb.c 2012-11-07 20:44:28 +0000
4004@@ -0,0 +1,5478 @@
4005+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*-
4006+ *
4007+ * Copyright (C) 2003,2004 Colin Walters <walters@gnome.org>
4008+ *
4009+ * This program is free software; you can redistribute it and/or modify
4010+ * it under the terms of the GNU General Public License as published by
4011+ * the Free Software Foundation; either version 2 of the License, or
4012+ * (at your option) any later version.
4013+ *
4014+ * The Rhythmbox authors hereby grant permission for non-GPL compatible
4015+ * GStreamer plugins to be used and distributed together with GStreamer
4016+ * and Rhythmbox. This permission is above and beyond the permissions granted
4017+ * by the GPL license by which Rhythmbox is covered. If you modify this code
4018+ * you may extend this exception to your version of the code, but you are not
4019+ * obligated to do so. If you do not wish to do so, delete this exception
4020+ * statement from your version.
4021+ *
4022+ * This program is distributed in the hope that it will be useful,
4023+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
4024+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
4025+ * GNU General Public License for more details.
4026+ *
4027+ * You should have received a copy of the GNU General Public License
4028+ * along with this program; if not, write to the Free Software
4029+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
4030+ *
4031+ */
4032+
4033+/**
4034+ * SECTION:rhythmdb
4035+ * @short_description: Rhythmbox database functions
4036+ *
4037+ * RhythmDB is an in-memory database containing #RhythmDBEntry items. It
4038+ * runs queries represented as #GPtrArray<!-- -->s containing query criteria,
4039+ * feeding the results into #RhythmDBQueryResults implementations such as
4040+ * #RhythmDBQueryModel. From there, entries are grouped by particular property
4041+ * values to form #RhythmDBPropertyModel<!-- -->s.
4042+ *
4043+ * #RhythmDBEntry contains a fixed set of properties, defined by #RhythmDBPropType,
4044+ */
4045+
4046+#include "config.h"
4047+
4048+#define G_IMPLEMENT_INLINES 1
4049+#define __RHYTHMDB_C__
4050+#include "rhythmdb.h"
4051+#undef G_IMPLEMENT_INLINES
4052+
4053+#include <string.h>
4054+#include <libxml/tree.h>
4055+#include <glib.h>
4056+#include <glib-object.h>
4057+#include <glib/gi18n.h>
4058+#include <gio/gio.h>
4059+#include <gobject/gvaluecollector.h>
4060+#include <gdk/gdk.h>
4061+
4062+
4063+#include "rb-marshal.h"
4064+#include "rb-file-helpers.h"
4065+#include "rb-debug.h"
4066+#include "rb-util.h"
4067+#include "rb-cut-and-paste-code.h"
4068+#include "rhythmdb-private.h"
4069+#include "rhythmdb-property-model.h"
4070+#include "rb-dialog.h"
4071+#include "rb-string-value-map.h"
4072+#include "rb-async-queue-watch.h"
4073+#include "rb-podcast-entry-types.h"
4074+#include "rb-gst-media-types.h"
4075+
4076+#define PROP_ENTRY(p,t,n) { RHYTHMDB_PROP_ ## p, "RHYTHMDB_PROP_" #p "", t, n }
4077+
4078+typedef struct _RhythmDBPropertyDef {
4079+ RhythmDBPropType prop_id;
4080+ const char *prop_name;
4081+ GType prop_type;
4082+ const char *elt_name;
4083+} RhythmDBPropertyDef;
4084+
4085+static const RhythmDBPropertyDef rhythmdb_properties[] = {
4086+ PROP_ENTRY(TYPE, G_TYPE_OBJECT, "type"),
4087+ PROP_ENTRY(ENTRY_ID, G_TYPE_ULONG, "entry-id"),
4088+ PROP_ENTRY(TITLE, G_TYPE_STRING, "title"),
4089+ PROP_ENTRY(GENRE, G_TYPE_STRING, "genre"),
4090+ PROP_ENTRY(ARTIST, G_TYPE_STRING, "artist"),
4091+ PROP_ENTRY(ALBUM, G_TYPE_STRING, "album"),
4092+ PROP_ENTRY(TRACK_NUMBER, G_TYPE_ULONG, "track-number"),
4093+ PROP_ENTRY(DISC_NUMBER, G_TYPE_ULONG, "disc-number"),
4094+ PROP_ENTRY(DURATION, G_TYPE_ULONG, "duration"),
4095+ PROP_ENTRY(FILE_SIZE, G_TYPE_UINT64, "file-size"),
4096+ PROP_ENTRY(LOCATION, G_TYPE_STRING, "location"),
4097+ PROP_ENTRY(MOUNTPOINT, G_TYPE_STRING, "mountpoint"),
4098+ PROP_ENTRY(MTIME, G_TYPE_ULONG, "mtime"),
4099+ PROP_ENTRY(FIRST_SEEN, G_TYPE_ULONG, "first-seen"),
4100+ PROP_ENTRY(LAST_SEEN, G_TYPE_ULONG, "last-seen"),
4101+ PROP_ENTRY(RATING, G_TYPE_DOUBLE, "rating"),
4102+ PROP_ENTRY(PLAY_COUNT, G_TYPE_ULONG, "play-count"),
4103+ PROP_ENTRY(LAST_PLAYED, G_TYPE_ULONG, "last-played"),
4104+ PROP_ENTRY(BITRATE, G_TYPE_ULONG, "bitrate"),
4105+ PROP_ENTRY(DATE, G_TYPE_ULONG, "date"),
4106+ PROP_ENTRY(TRACK_GAIN, G_TYPE_DOUBLE, "replaygain-track-gain"),
4107+ PROP_ENTRY(TRACK_PEAK, G_TYPE_DOUBLE, "replaygain-track-peak"),
4108+ PROP_ENTRY(ALBUM_GAIN, G_TYPE_DOUBLE, "replaygain-album-gain"),
4109+ PROP_ENTRY(ALBUM_PEAK, G_TYPE_DOUBLE, "replaygain-album-peak"),
4110+ PROP_ENTRY(MEDIA_TYPE, G_TYPE_STRING, "media-type"),
4111+ PROP_ENTRY(TITLE_SORT_KEY, G_TYPE_STRING, "title-sort-key"),
4112+ PROP_ENTRY(GENRE_SORT_KEY, G_TYPE_STRING, "genre-sort-key"),
4113+ PROP_ENTRY(ARTIST_SORT_KEY, G_TYPE_STRING, "artist-sort-key"),
4114+ PROP_ENTRY(ALBUM_SORT_KEY, G_TYPE_STRING, "album-sort-key"),
4115+ PROP_ENTRY(TITLE_FOLDED, G_TYPE_STRING, "title-folded"),
4116+ PROP_ENTRY(GENRE_FOLDED, G_TYPE_STRING, "genre-folded"),
4117+ PROP_ENTRY(ARTIST_FOLDED, G_TYPE_STRING, "artist-folded"),
4118+ PROP_ENTRY(ALBUM_FOLDED, G_TYPE_STRING, "album-folded"),
4119+ PROP_ENTRY(LAST_PLAYED_STR, G_TYPE_STRING, "last-played-str"),
4120+ PROP_ENTRY(HIDDEN, G_TYPE_BOOLEAN, "hidden"),
4121+ PROP_ENTRY(PLAYBACK_ERROR, G_TYPE_STRING, "playback-error"),
4122+ PROP_ENTRY(FIRST_SEEN_STR, G_TYPE_STRING, "first-seen-str"),
4123+ PROP_ENTRY(LAST_SEEN_STR, G_TYPE_STRING, "last-seen-str"),
4124+
4125+ PROP_ENTRY(SEARCH_MATCH, G_TYPE_STRING, "search-match"),
4126+ PROP_ENTRY(YEAR, G_TYPE_ULONG, "year"),
4127+ PROP_ENTRY(KEYWORD, G_TYPE_STRING, "keyword"),
4128+
4129+ PROP_ENTRY(STATUS, G_TYPE_ULONG, "status"),
4130+ PROP_ENTRY(DESCRIPTION, G_TYPE_STRING, "description"),
4131+ PROP_ENTRY(SUBTITLE, G_TYPE_STRING, "subtitle"),
4132+ PROP_ENTRY(SUMMARY, G_TYPE_STRING, "summary"),
4133+ PROP_ENTRY(LANG, G_TYPE_STRING, "lang"),
4134+ PROP_ENTRY(COPYRIGHT, G_TYPE_STRING, "copyright"),
4135+ PROP_ENTRY(IMAGE, G_TYPE_STRING, "image"),
4136+ PROP_ENTRY(POST_TIME, G_TYPE_ULONG, "post-time"),
4137+
4138+ PROP_ENTRY(MUSICBRAINZ_TRACKID, G_TYPE_STRING, "mb-trackid"),
4139+ PROP_ENTRY(MUSICBRAINZ_ARTISTID, G_TYPE_STRING, "mb-artistid"),
4140+ PROP_ENTRY(MUSICBRAINZ_ALBUMID, G_TYPE_STRING, "mb-albumid"),
4141+ PROP_ENTRY(MUSICBRAINZ_ALBUMARTISTID, G_TYPE_STRING, "mb-albumartistid"),
4142+ PROP_ENTRY(ARTIST_SORTNAME, G_TYPE_STRING, "mb-artistsortname"),
4143+ PROP_ENTRY(ALBUM_SORTNAME, G_TYPE_STRING, "album-sortname"),
4144+
4145+ PROP_ENTRY(ARTIST_SORTNAME_SORT_KEY, G_TYPE_STRING, "artist-sortname-sort-key"),
4146+ PROP_ENTRY(ARTIST_SORTNAME_FOLDED, G_TYPE_STRING, "artist-sortname-folded"),
4147+ PROP_ENTRY(ALBUM_SORTNAME_SORT_KEY, G_TYPE_STRING, "album-sortname-sort-key"),
4148+ PROP_ENTRY(ALBUM_SORTNAME_FOLDED, G_TYPE_STRING, "album-sortname-folded"),
4149+
4150+ PROP_ENTRY(COMMENT, G_TYPE_STRING, "comment"),
4151+
4152+ PROP_ENTRY(ALBUM_ARTIST, G_TYPE_STRING, "album-artist"),
4153+ PROP_ENTRY(ALBUM_ARTIST_SORT_KEY, G_TYPE_STRING, "album-artist-sort-key"),
4154+ PROP_ENTRY(ALBUM_ARTIST_FOLDED, G_TYPE_STRING, "album-artist-folded"),
4155+ PROP_ENTRY(ALBUM_ARTIST_SORTNAME, G_TYPE_STRING, "album-artist-sortname"),
4156+ PROP_ENTRY(ALBUM_ARTIST_SORTNAME_SORT_KEY, G_TYPE_STRING, "album-artist-sortname-sort-key"),
4157+ PROP_ENTRY(ALBUM_ARTIST_SORTNAME_FOLDED, G_TYPE_STRING, "album-artist-sortname-folded"),
4158+
4159+ PROP_ENTRY(BPM, G_TYPE_DOUBLE, "beats-per-minute"),
4160+
4161+ { 0, 0, 0, 0 }
4162+};
4163+
4164+#define RB_PARSE_NICK_START (xmlChar *) "["
4165+#define RB_PARSE_NICK_END (xmlChar *) "]"
4166+
4167+
4168+#define RHYTHMDB_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), RHYTHMDB_TYPE, RhythmDBPrivate))
4169+G_DEFINE_ABSTRACT_TYPE(RhythmDB, rhythmdb, G_TYPE_OBJECT)
4170+
4171+/* file attributes requested in RHYTHMDB_ACTION_STAT and RHYTHMDB_ACTION_LOAD */
4172+#define RHYTHMDB_FILE_INFO_ATTRIBUTES \
4173+ G_FILE_ATTRIBUTE_STANDARD_SIZE "," \
4174+ G_FILE_ATTRIBUTE_STANDARD_DISPLAY_NAME "," \
4175+ G_FILE_ATTRIBUTE_STANDARD_TYPE "," \
4176+ G_FILE_ATTRIBUTE_TIME_MODIFIED
4177+
4178+/* file attributes requested in RHYTHMDB_ACTION_ENUM_DIR */
4179+#define RHYTHMDB_FILE_CHILD_INFO_ATTRIBUTES \
4180+ RHYTHMDB_FILE_INFO_ATTRIBUTES "," \
4181+ G_FILE_ATTRIBUTE_STANDARD_IS_HIDDEN "," \
4182+ G_FILE_ATTRIBUTE_STANDARD_NAME
4183+
4184+/*
4185+ * Filters for MIME/media types to ignore.
4186+ * The only complication here is that there are some application/ types that
4187+ * are used for audio/video files. Otherwise, we'd ignore everything except
4188+ * audio/ and video/.
4189+ */
4190+struct media_type_filter {
4191+ const char *prefix;
4192+ gboolean ignore;
4193+} media_type_filters[] = {
4194+ { "image/", TRUE },
4195+ { "text/", TRUE },
4196+ { "application/ogg", FALSE },
4197+ { "application/x-id3", FALSE },
4198+ { "application/x-apetag", FALSE },
4199+ { "application/x-3gp", FALSE },
4200+ { "application/x-annodex", FALSE },
4201+ { "application/", TRUE },
4202+};
4203+
4204+/*
4205+ * File size below which we will simply ignore files that can't be identified.
4206+ * This is mostly here so we ignore the various text files that are packaged
4207+ * with many netlabel releases and other downloads.
4208+ */
4209+#define REALLY_SMALL_FILE_SIZE (4096)
4210+
4211+
4212+typedef struct
4213+{
4214+ RhythmDB *db;
4215+ GPtrArray *query;
4216+ guint propid;
4217+ RhythmDBQueryResults *results;
4218+ gboolean cancel;
4219+} RhythmDBQueryThreadData;
4220+
4221+typedef struct
4222+{
4223+ RhythmDB *db;
4224+ RhythmDBEntryType *type;
4225+ RhythmDBEntryType *ignore_type;
4226+ RhythmDBEntryType *error_type;
4227+} RhythmDBAddThreadData;
4228+
4229+typedef struct
4230+{
4231+ enum {
4232+ RHYTHMDB_ACTION_STAT,
4233+ RHYTHMDB_ACTION_LOAD,
4234+ RHYTHMDB_ACTION_ENUM_DIR,
4235+ RHYTHMDB_ACTION_SYNC,
4236+ RHYTHMDB_ACTION_QUIT,
4237+ } type;
4238+ RBRefString *uri;
4239+ union {
4240+ struct {
4241+ RhythmDBEntryType *entry_type;
4242+ RhythmDBEntryType *ignore_type;
4243+ RhythmDBEntryType *error_type;
4244+ } types;
4245+ GSList *changes;
4246+ } data;
4247+} RhythmDBAction;
4248+
4249+static void rhythmdb_dispose (GObject *object);
4250+static void rhythmdb_finalize (GObject *object);
4251+static void rhythmdb_set_property (GObject *object,
4252+ guint prop_id,
4253+ const GValue *value,
4254+ GParamSpec *pspec);
4255+static void rhythmdb_get_property (GObject *object,
4256+ guint prop_id,
4257+ GValue *value,
4258+ GParamSpec *pspec);
4259+static void rhythmdb_thread_create (RhythmDB *db,
4260+ GThreadPool *pool,
4261+ GThreadFunc func,
4262+ gpointer data);
4263+static void rhythmdb_read_enter (RhythmDB *db);
4264+static void rhythmdb_read_leave (RhythmDB *db);
4265+static void rhythmdb_process_one_event (RhythmDBEvent *event, RhythmDB *db);
4266+static gpointer action_thread_main (RhythmDB *db);
4267+static gpointer query_thread_main (RhythmDBQueryThreadData *data);
4268+static void rhythmdb_entry_set_mount_point (RhythmDB *db,
4269+ RhythmDBEntry *entry,
4270+ const gchar *realuri);
4271+
4272+static gboolean rhythmdb_idle_save (RhythmDB *db);
4273+static void db_settings_changed_cb (GSettings *settings, const char *key, RhythmDB *db);
4274+static void rhythmdb_sync_library_location (RhythmDB *db);
4275+static void rhythmdb_entry_sync_mirrored (RhythmDBEntry *entry,
4276+ guint propid);
4277+static gboolean rhythmdb_entry_extra_metadata_accumulator (GSignalInvocationHint *ihint,
4278+ GValue *return_accu,
4279+ const GValue *handler_return,
4280+ gpointer data);
4281+
4282+static void rhythmdb_event_free (RhythmDB *db, RhythmDBEvent *event);
4283+static void rhythmdb_add_to_stat_list (RhythmDB *db,
4284+ const char *uri,
4285+ RhythmDBEntry *entry,
4286+ RhythmDBEntryType *type,
4287+ RhythmDBEntryType *ignore_type,
4288+ RhythmDBEntryType *error_type);
4289+static void free_entry_changes (GSList *entry_changes);
4290+
4291+static void perform_next_mount (RhythmDB *db);
4292+
4293+enum
4294+{
4295+ PROP_0,
4296+ PROP_NAME,
4297+ PROP_DRY_RUN,
4298+ PROP_NO_UPDATE,
4299+};
4300+
4301+enum
4302+{
4303+ ENTRY_ADDED,
4304+ ENTRY_CHANGED,
4305+ ENTRY_DELETED,
4306+ ENTRY_KEYWORD_ADDED,
4307+ ENTRY_KEYWORD_REMOVED,
4308+ ENTRY_EXTRA_METADATA_REQUEST,
4309+ ENTRY_EXTRA_METADATA_NOTIFY,
4310+ ENTRY_EXTRA_METADATA_GATHER,
4311+ LOAD_COMPLETE,
4312+ SAVE_COMPLETE,
4313+ SAVE_ERROR,
4314+ READ_ONLY,
4315+ CREATE_MOUNT_OP,
4316+ LAST_SIGNAL
4317+};
4318+
4319+static guint rhythmdb_signals[LAST_SIGNAL] = { 0 };
4320+
4321+static void
4322+rhythmdb_class_init (RhythmDBClass *klass)
4323+{
4324+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
4325+
4326+ object_class->dispose = rhythmdb_dispose;
4327+ object_class->finalize = rhythmdb_finalize;
4328+
4329+ object_class->set_property = rhythmdb_set_property;
4330+ object_class->get_property = rhythmdb_get_property;
4331+
4332+ /**
4333+ * RhythmDB:name
4334+ *
4335+ * Database name. Not sure whta this is used for.
4336+ */
4337+ g_object_class_install_property (object_class,
4338+ PROP_NAME,
4339+ g_param_spec_string ("name",
4340+ "name",
4341+ "name",
4342+ NULL,
4343+ G_PARAM_READWRITE));
4344+ /**
4345+ * RhythmDB:dry-run
4346+ *
4347+ * If %TRUE, no metadata changes will be written back to media fies.
4348+ */
4349+ g_object_class_install_property (object_class,
4350+ PROP_DRY_RUN,
4351+ g_param_spec_boolean ("dry-run",
4352+ "dry run",
4353+ "Whether or not changes should be saved",
4354+ FALSE,
4355+ G_PARAM_READWRITE));
4356+ /**
4357+ * RhythmDB:no-update
4358+ *
4359+ * If %TRUE, the database will not be updated.
4360+ */
4361+ g_object_class_install_property (object_class,
4362+ PROP_NO_UPDATE,
4363+ g_param_spec_boolean ("no-update",
4364+ "no update",
4365+ "Whether or not to update the database",
4366+ FALSE,
4367+ G_PARAM_READWRITE));
4368+ /**
4369+ * RhythmDB::entry-added:
4370+ * @db: the #RhythmDB
4371+ * @entry: the newly added #RhythmDBEntry
4372+ *
4373+ * Emitted when a new entry is added to the database.
4374+ */
4375+ rhythmdb_signals[ENTRY_ADDED] =
4376+ g_signal_new ("entry_added",
4377+ RHYTHMDB_TYPE,
4378+ G_SIGNAL_RUN_LAST,
4379+ G_STRUCT_OFFSET (RhythmDBClass, entry_added),
4380+ NULL, NULL,
4381+ g_cclosure_marshal_VOID__BOXED,
4382+ G_TYPE_NONE,
4383+ 1, RHYTHMDB_TYPE_ENTRY);
4384+
4385+ /**
4386+ * RhythmDB::entry-deleted:
4387+ * @db: the #RhythmDB
4388+ * @entry: the deleted #RhythmDBEntry
4389+ *
4390+ * Emitted when an entry is deleted from the database.
4391+ */
4392+ rhythmdb_signals[ENTRY_DELETED] =
4393+ g_signal_new ("entry_deleted",
4394+ RHYTHMDB_TYPE,
4395+ G_SIGNAL_RUN_LAST,
4396+ G_STRUCT_OFFSET (RhythmDBClass, entry_deleted),
4397+ NULL, NULL,
4398+ g_cclosure_marshal_VOID__BOXED,
4399+ G_TYPE_NONE,
4400+ 1, RHYTHMDB_TYPE_ENTRY);
4401+
4402+ /**
4403+ * RhythmDB::entry-changed:
4404+ * @db: the #RhythmDB
4405+ * @entry: the changed #RhythmDBEntry
4406+ * @changes: a #GValueArray of #RhythmDBEntryChange structures describing the changes
4407+ *
4408+ * Emitted when a database entry is modified. The @changes list
4409+ * contains a structure for each entry property that has been modified.
4410+ */
4411+ rhythmdb_signals[ENTRY_CHANGED] =
4412+ g_signal_new ("entry_changed",
4413+ RHYTHMDB_TYPE,
4414+ G_SIGNAL_RUN_LAST,
4415+ G_STRUCT_OFFSET (RhythmDBClass, entry_changed),
4416+ NULL, NULL,
4417+ rb_marshal_VOID__BOXED_BOXED,
4418+ G_TYPE_NONE, 2,
4419+ RHYTHMDB_TYPE_ENTRY, G_TYPE_VALUE_ARRAY);
4420+
4421+ /**
4422+ * RhythmDB::entry-keyword-added:
4423+ * @db: the #RhythmDB
4424+ * @entry: the #RhythmDBEntry to which a keyword has been added
4425+ * @keyword: the keyword that was added
4426+ *
4427+ * Emitted when a keyword is added to an entry.
4428+ */
4429+ rhythmdb_signals[ENTRY_KEYWORD_ADDED] =
4430+ g_signal_new ("entry_keyword_added",
4431+ RHYTHMDB_TYPE,
4432+ G_SIGNAL_RUN_LAST,
4433+ G_STRUCT_OFFSET (RhythmDBClass, entry_added),
4434+ NULL, NULL,
4435+ rb_marshal_VOID__BOXED_BOXED,
4436+ G_TYPE_NONE,
4437+ 2, RHYTHMDB_TYPE_ENTRY, RB_TYPE_REFSTRING);
4438+
4439+ /**
4440+ * RhythmDB::entry-keyword-removed:
4441+ * @db: the #RhythmDB
4442+ * @entry: the #RhythmDBEntry from which a keyword has been removed
4443+ * @keyword: the keyword that was removed
4444+ *
4445+ * Emitted when a keyword is removed from an entry.
4446+ */
4447+ rhythmdb_signals[ENTRY_KEYWORD_REMOVED] =
4448+ g_signal_new ("entry_keyword_removed",
4449+ RHYTHMDB_TYPE,
4450+ G_SIGNAL_RUN_LAST,
4451+ G_STRUCT_OFFSET (RhythmDBClass, entry_deleted),
4452+ NULL, NULL,
4453+ rb_marshal_VOID__BOXED_BOXED,
4454+ G_TYPE_NONE,
4455+ 2, RHYTHMDB_TYPE_ENTRY, RB_TYPE_REFSTRING);
4456+
4457+ /**
4458+ * RhythmDB::entry-extra-metadata-request:
4459+ * @db: the #RhythmDB
4460+ * @entry: the #RhythmDBEntry for which extra metadata is being requested
4461+ *
4462+ * This signal is emitted to allow extra (transient) metadata to be supplied
4463+ * for the given entry. The detail of the signal invocation describes the
4464+ * specific metadata value being requested. If the object handling the signal
4465+ * can provide the requested item, but it isn't immediately available, it can
4466+ * initiate an attempt to retrieve it. If successful, it would call
4467+ * @rhythmdb_emit_entry_extra_metadata_notify when the metadata is available.
4468+ *
4469+ * Return value: the extra metadata value
4470+ */
4471+ rhythmdb_signals[ENTRY_EXTRA_METADATA_REQUEST] =
4472+ g_signal_new ("entry_extra_metadata_request",
4473+ G_OBJECT_CLASS_TYPE (object_class),
4474+ G_SIGNAL_RUN_LAST | G_SIGNAL_DETAILED,
4475+ G_STRUCT_OFFSET (RhythmDBClass, entry_extra_metadata_request),
4476+ rhythmdb_entry_extra_metadata_accumulator, NULL,
4477+ rb_marshal_BOXED__BOXED,
4478+ G_TYPE_VALUE, 1,
4479+ RHYTHMDB_TYPE_ENTRY);
4480+
4481+ /**
4482+ * RhythmDB::entry-extra-metadata-notify:
4483+ * @db: the #RhythmDB
4484+ * @entry: the #RhythmDBEntry for which extra metadata has been supplied
4485+ * @field: the extra metadata field being supplied
4486+ * @metadata: the extra metadata value
4487+ *
4488+ * This signal is emitted when an extra metadata value is provided for a specific
4489+ * entry independantly of an extra metadata request.
4490+ */
4491+ rhythmdb_signals[ENTRY_EXTRA_METADATA_NOTIFY] =
4492+ g_signal_new ("entry_extra_metadata_notify",
4493+ G_OBJECT_CLASS_TYPE (object_class),
4494+ G_SIGNAL_RUN_LAST | G_SIGNAL_DETAILED,
4495+ G_STRUCT_OFFSET (RhythmDBClass, entry_extra_metadata_notify),
4496+ NULL, NULL,
4497+ rb_marshal_VOID__BOXED_STRING_BOXED,
4498+ G_TYPE_NONE, 3,
4499+ RHYTHMDB_TYPE_ENTRY, G_TYPE_STRING, G_TYPE_VALUE);
4500+
4501+ /**
4502+ * RhythmDB::entry-extra-metadata-gather:
4503+ * @db: the #RhythmDB
4504+ * @entry: the #RhythmDBEntry for which to gather metadata
4505+ * @data: a #RBStringValueMap to hold the gathered metadata
4506+ *
4507+ * Emitted to gather all available extra metadata for a database entry.
4508+ * Handlers for this signal should insert any metadata they can provide
4509+ * into the string-value map. Only immediately available metadata
4510+ * items should be returned. If one or more metadata items is not
4511+ * immediately available, the handler should not initiate an attempt to
4512+ * retrieve them.
4513+ */
4514+ rhythmdb_signals[ENTRY_EXTRA_METADATA_GATHER] =
4515+ g_signal_new ("entry_extra_metadata_gather",
4516+ G_OBJECT_CLASS_TYPE (object_class),
4517+ G_SIGNAL_RUN_LAST,
4518+ G_STRUCT_OFFSET (RhythmDBClass, entry_extra_metadata_gather),
4519+ NULL, NULL,
4520+ rb_marshal_VOID__BOXED_OBJECT,
4521+ G_TYPE_NONE, 2,
4522+ RHYTHMDB_TYPE_ENTRY, RB_TYPE_STRING_VALUE_MAP);
4523+
4524+ /**
4525+ * RhythmDB::load-complete:
4526+ * @db: the #RhythmDB
4527+ *
4528+ * Emitted when the database is fully loaded.
4529+ */
4530+ rhythmdb_signals[LOAD_COMPLETE] =
4531+ g_signal_new ("load_complete",
4532+ RHYTHMDB_TYPE,
4533+ G_SIGNAL_RUN_LAST,
4534+ G_STRUCT_OFFSET (RhythmDBClass, load_complete),
4535+ NULL, NULL,
4536+ g_cclosure_marshal_VOID__VOID,
4537+ G_TYPE_NONE,
4538+ 0);
4539+
4540+ /**
4541+ * RhythmDB::save-complete:
4542+ * @db: the #RhythmDB
4543+ *
4544+ * Emitted when the database has been saved.
4545+ */
4546+ rhythmdb_signals[SAVE_COMPLETE] =
4547+ g_signal_new ("save_complete",
4548+ RHYTHMDB_TYPE,
4549+ G_SIGNAL_RUN_LAST,
4550+ G_STRUCT_OFFSET (RhythmDBClass, save_complete),
4551+ NULL, NULL,
4552+ g_cclosure_marshal_VOID__VOID,
4553+ G_TYPE_NONE,
4554+ 0);
4555+
4556+ /**
4557+ * RhythmDB::save-error:
4558+ * @db: the #RhythmDB
4559+ * @uri: URI of the database file
4560+ * @error: the error that occurred
4561+ *
4562+ * Emitted when an error occurs while saving the database.
4563+ */
4564+ rhythmdb_signals[SAVE_ERROR] =
4565+ g_signal_new ("save-error",
4566+ G_OBJECT_CLASS_TYPE (object_class),
4567+ G_SIGNAL_RUN_LAST,
4568+ G_STRUCT_OFFSET (RhythmDBClass, save_error),
4569+ NULL, NULL,
4570+ rb_marshal_VOID__STRING_POINTER,
4571+ G_TYPE_NONE,
4572+ 2,
4573+ G_TYPE_STRING,
4574+ G_TYPE_POINTER);
4575+
4576+ /**
4577+ * RhythmDB::read-only:
4578+ * @db: the #RhythmDB
4579+ * @readonly: %TRUE if the database is read-only
4580+ *
4581+ * Emitted when the database becomes temporarily read-only, or becomes
4582+ * writeable after being read-only.
4583+ */
4584+ rhythmdb_signals[READ_ONLY] =
4585+ g_signal_new ("read-only",
4586+ G_OBJECT_CLASS_TYPE (object_class),
4587+ G_SIGNAL_RUN_LAST,
4588+ G_STRUCT_OFFSET (RhythmDBClass, read_only),
4589+ NULL, NULL,
4590+ g_cclosure_marshal_VOID__BOOLEAN,
4591+ G_TYPE_NONE,
4592+ 1,
4593+ G_TYPE_BOOLEAN);
4594+
4595+ /**
4596+ * RhythmDB::create-mount-op:
4597+ * @db: the #RhythmDB
4598+ *
4599+ * Emitted to request creation of a #GMountOperation to use to mount a volume.
4600+ *
4601+ * Returns: (transfer full): a #GMountOperation (usually actually a #GtkMountOperation)
4602+ */
4603+ rhythmdb_signals[CREATE_MOUNT_OP] =
4604+ g_signal_new ("create-mount-op",
4605+ G_OBJECT_CLASS_TYPE (object_class),
4606+ G_SIGNAL_RUN_LAST,
4607+ 0, /* no need for an internal handler */
4608+ rb_signal_accumulator_object_handled, NULL,
4609+ rb_marshal_OBJECT__VOID,
4610+ G_TYPE_MOUNT_OPERATION,
4611+ 0);
4612+
4613+ g_type_class_add_private (klass, sizeof (RhythmDBPrivate));
4614+}
4615+
4616+static void
4617+rhythmdb_push_event (RhythmDB *db, RhythmDBEvent *event)
4618+{
4619+ g_async_queue_push (db->priv->event_queue, event);
4620+ g_main_context_wakeup (g_main_context_default ());
4621+}
4622+
4623+static gboolean
4624+metadata_field_from_prop (RhythmDBPropType prop,
4625+ RBMetaDataField *field)
4626+{
4627+ switch (prop) {
4628+ case RHYTHMDB_PROP_TITLE:
4629+ *field = RB_METADATA_FIELD_TITLE;
4630+ return TRUE;
4631+ case RHYTHMDB_PROP_ARTIST:
4632+ *field = RB_METADATA_FIELD_ARTIST;
4633+ return TRUE;
4634+ case RHYTHMDB_PROP_ALBUM:
4635+ *field = RB_METADATA_FIELD_ALBUM;
4636+ return TRUE;
4637+ case RHYTHMDB_PROP_GENRE:
4638+ *field = RB_METADATA_FIELD_GENRE;
4639+ return TRUE;
4640+ case RHYTHMDB_PROP_COMMENT:
4641+ *field = RB_METADATA_FIELD_COMMENT;
4642+ return TRUE;
4643+ case RHYTHMDB_PROP_TRACK_NUMBER:
4644+ *field = RB_METADATA_FIELD_TRACK_NUMBER;
4645+ return TRUE;
4646+ case RHYTHMDB_PROP_DISC_NUMBER:
4647+ *field = RB_METADATA_FIELD_DISC_NUMBER;
4648+ return TRUE;
4649+ case RHYTHMDB_PROP_DATE:
4650+ *field = RB_METADATA_FIELD_DATE;
4651+ return TRUE;
4652+ case RHYTHMDB_PROP_BPM:
4653+ *field = RB_METADATA_FIELD_BPM;
4654+ return TRUE;
4655+ case RHYTHMDB_PROP_MUSICBRAINZ_TRACKID:
4656+ *field = RB_METADATA_FIELD_MUSICBRAINZ_TRACKID;
4657+ return TRUE;
4658+ case RHYTHMDB_PROP_MUSICBRAINZ_ARTISTID:
4659+ *field = RB_METADATA_FIELD_MUSICBRAINZ_ARTISTID;
4660+ return TRUE;
4661+ case RHYTHMDB_PROP_MUSICBRAINZ_ALBUMID:
4662+ *field = RB_METADATA_FIELD_MUSICBRAINZ_ALBUMID;
4663+ return TRUE;
4664+ case RHYTHMDB_PROP_MUSICBRAINZ_ALBUMARTISTID:
4665+ *field = RB_METADATA_FIELD_MUSICBRAINZ_ALBUMARTISTID;
4666+ return TRUE;
4667+ case RHYTHMDB_PROP_ARTIST_SORTNAME:
4668+ *field = RB_METADATA_FIELD_ARTIST_SORTNAME;
4669+ return TRUE;
4670+ case RHYTHMDB_PROP_ALBUM_SORTNAME:
4671+ *field = RB_METADATA_FIELD_ALBUM_SORTNAME;
4672+ return TRUE;
4673+ case RHYTHMDB_PROP_ALBUM_ARTIST:
4674+ *field = RB_METADATA_FIELD_ALBUM_ARTIST;
4675+ return TRUE;
4676+ case RHYTHMDB_PROP_ALBUM_ARTIST_SORTNAME:
4677+ *field = RB_METADATA_FIELD_ALBUM_ARTIST_SORTNAME;
4678+ return TRUE;
4679+ default:
4680+ return FALSE;
4681+ }
4682+}
4683+
4684+static void
4685+rhythmdb_init (RhythmDB *db)
4686+{
4687+ guint i;
4688+ GEnumClass *prop_class;
4689+
4690+ db->priv = RHYTHMDB_GET_PRIVATE (db);
4691+
4692+ db->priv->settings = g_settings_new ("org.gnome.rhythmbox.rhythmdb");
4693+ g_signal_connect_object (db->priv->settings, "changed", G_CALLBACK (db_settings_changed_cb), db, 0);
4694+
4695+ db->priv->action_queue = g_async_queue_new ();
4696+ db->priv->event_queue = g_async_queue_new ();
4697+ db->priv->delayed_write_queue = g_async_queue_new ();
4698+ db->priv->event_queue_watch_id = rb_async_queue_watch_new (db->priv->event_queue,
4699+ G_PRIORITY_LOW, /* really? */
4700+ (RBAsyncQueueWatchFunc) rhythmdb_process_one_event,
4701+ db,
4702+ NULL,
4703+ NULL);
4704+
4705+ db->priv->restored_queue = g_async_queue_new ();
4706+
4707+ db->priv->query_thread_pool = g_thread_pool_new ((GFunc)query_thread_main,
4708+ NULL,
4709+ -1, FALSE, NULL);
4710+
4711+ db->priv->metadata = rb_metadata_new ();
4712+
4713+ prop_class = g_type_class_ref (RHYTHMDB_TYPE_PROP_TYPE);
4714+
4715+ g_assert (prop_class->n_values == RHYTHMDB_NUM_PROPERTIES);
4716+
4717+ g_type_class_unref (prop_class);
4718+
4719+ db->priv->propname_map = g_hash_table_new (g_str_hash, g_str_equal);
4720+
4721+ for (i = 0; i < RHYTHMDB_NUM_PROPERTIES; i++) {
4722+ const xmlChar *name = rhythmdb_nice_elt_name_from_propid (db, i);
4723+ g_hash_table_insert (db->priv->propname_map, (gpointer) name, GINT_TO_POINTER (i));
4724+ }
4725+
4726+ db->priv->entry_type_map = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_object_unref);
4727+ db->priv->entry_type_map_mutex = g_mutex_new ();
4728+ db->priv->entry_type_mutex = g_mutex_new ();
4729+
4730+ rhythmdb_register_song_entry_types (db);
4731+ rb_podcast_register_entry_types (db);
4732+
4733+ db->priv->stat_mutex = g_mutex_new ();
4734+
4735+ db->priv->change_mutex = g_mutex_new ();
4736+
4737+ db->priv->changed_entries = g_hash_table_new_full (NULL,
4738+ NULL,
4739+ (GDestroyNotify) rhythmdb_entry_unref,
4740+ NULL);
4741+ db->priv->added_entries = g_hash_table_new_full (NULL,
4742+ NULL,
4743+ (GDestroyNotify) rhythmdb_entry_unref,
4744+ NULL);
4745+ db->priv->deleted_entries = g_hash_table_new_full (NULL,
4746+ NULL,
4747+ (GDestroyNotify) rhythmdb_entry_unref,
4748+ NULL);
4749+
4750+ db->priv->saving_condition = g_cond_new ();
4751+ db->priv->saving_mutex = g_mutex_new ();
4752+
4753+ db->priv->can_save = TRUE;
4754+ db->priv->exiting = g_cancellable_new ();
4755+ db->priv->saving = FALSE;
4756+ db->priv->dirty = FALSE;
4757+
4758+ db->priv->empty_string = rb_refstring_new ("");
4759+ db->priv->octet_stream_str = rb_refstring_new ("application/octet-stream");
4760+
4761+ db->priv->next_entry_id = 1;
4762+
4763+ rhythmdb_init_monitoring (db);
4764+
4765+ rhythmdb_dbus_register (db);
4766+}
4767+
4768+static GError *
4769+make_access_failed_error (const char *uri, GError *access_error)
4770+{
4771+ char *unescaped;
4772+ char *utf8ised;
4773+ GError *error;
4774+
4775+ /* make sure the URI we put in the error message is valid utf8 */
4776+ unescaped = g_uri_unescape_string (uri, NULL);
4777+ utf8ised = rb_make_valid_utf8 (unescaped, '?');
4778+
4779+ error = g_error_new (RHYTHMDB_ERROR,
4780+ RHYTHMDB_ERROR_ACCESS_FAILED,
4781+ _("Couldn't access %s: %s"),
4782+ utf8ised,
4783+ access_error->message);
4784+ rb_debug ("got error on %s: %s", uri, error->message);
4785+ g_free (unescaped);
4786+ g_free (utf8ised);
4787+ return error;
4788+}
4789+
4790+static gboolean
4791+rhythmdb_ignore_media_type (const char *media_type)
4792+{
4793+ int i;
4794+
4795+ for (i = 0; i < G_N_ELEMENTS (media_type_filters); i++) {
4796+ if (g_str_has_prefix (media_type, media_type_filters[i].prefix)) {
4797+ return media_type_filters[i].ignore;
4798+ }
4799+ }
4800+ return FALSE;
4801+}
4802+
4803+typedef struct {
4804+ RhythmDB *db;
4805+ GList *stat_list;
4806+} RhythmDBStatThreadData;
4807+
4808+static gpointer
4809+stat_thread_main (RhythmDBStatThreadData *data)
4810+{
4811+ GList *i;
4812+ GError *error = NULL;
4813+ RhythmDBEvent *result;
4814+
4815+ data->db->priv->stat_thread_count = g_list_length (data->stat_list);
4816+ data->db->priv->stat_thread_done = 0;
4817+
4818+ rb_debug ("entering stat thread: %d to process", data->db->priv->stat_thread_count);
4819+ for (i = data->stat_list; i != NULL; i = i->next) {
4820+ RhythmDBEvent *event = (RhythmDBEvent *)i->data;
4821+ GFile *file;
4822+
4823+ /* if we've been cancelled, just free the event. this will
4824+ * clean up the list and then we'll exit the thread.
4825+ */
4826+ if (g_cancellable_is_cancelled (data->db->priv->exiting)) {
4827+ rhythmdb_event_free (data->db, event);
4828+ continue;
4829+ }
4830+
4831+ if (data->db->priv->stat_thread_done > 0 &&
4832+ data->db->priv->stat_thread_done % 1000 == 0) {
4833+ rb_debug ("%d file info queries done",
4834+ data->db->priv->stat_thread_done);
4835+ }
4836+
4837+ file = g_file_new_for_uri (rb_refstring_get (event->uri));
4838+ event->real_uri = rb_refstring_ref (event->uri); /* what? */
4839+ event->file_info = g_file_query_info (file,
4840+ G_FILE_ATTRIBUTE_TIME_MODIFIED, /* anything else? */
4841+ G_FILE_QUERY_INFO_NONE,
4842+ data->db->priv->exiting,
4843+ &error);
4844+ if (error != NULL) {
4845+ event->error = make_access_failed_error (rb_refstring_get (event->uri), error);
4846+ g_clear_error (&error);
4847+
4848+ if (event->file_info != NULL) {
4849+ g_object_unref (event->file_info);
4850+ event->file_info = NULL;
4851+ }
4852+ }
4853+
4854+ g_async_queue_push (data->db->priv->event_queue, event);
4855+ g_object_unref (file);
4856+ g_atomic_int_inc (&data->db->priv->stat_thread_done);
4857+ }
4858+
4859+ g_list_free (data->stat_list);
4860+
4861+ data->db->priv->stat_thread_running = FALSE;
4862+
4863+ rb_debug ("exiting stat thread");
4864+ result = g_slice_new0 (RhythmDBEvent);
4865+ result->db = data->db; /* need to unref? */
4866+ result->type = RHYTHMDB_EVENT_THREAD_EXITED;
4867+ rhythmdb_push_event (data->db, result);
4868+
4869+ g_free (data);
4870+ return NULL;
4871+}
4872+
4873+static void
4874+perform_next_mount_cb (GObject *file, GAsyncResult *res, RhythmDB *db)
4875+{
4876+ GError *error = NULL;
4877+
4878+ g_file_mount_enclosing_volume_finish (G_FILE (file), res, &error);
4879+ if (error != NULL) {
4880+ char *uri;
4881+
4882+ uri = g_file_get_uri (G_FILE (file));
4883+ rb_debug ("Unable to mount %s: %s", uri, error->message);
4884+ g_free (uri);
4885+ g_clear_error (&error);
4886+ }
4887+ g_object_unref (file);
4888+
4889+ perform_next_mount (db);
4890+}
4891+
4892+static void
4893+perform_next_mount (RhythmDB *db)
4894+{
4895+ GList *l;
4896+ char *mountpoint;
4897+ GMountOperation *mount_op = NULL;
4898+
4899+ if (db->priv->mount_list == NULL) {
4900+ rb_debug ("finished mounting");
4901+ return;
4902+ }
4903+
4904+ l = db->priv->mount_list;
4905+ db->priv->mount_list = db->priv->mount_list->next;
4906+ mountpoint = l->data;
4907+ g_list_free1 (l);
4908+
4909+ rb_debug ("mounting %s", (char *)mountpoint);
4910+ g_signal_emit (G_OBJECT (db), rhythmdb_signals[CREATE_MOUNT_OP], 0, &mount_op);
4911+ g_file_mount_enclosing_volume (g_file_new_for_uri (mountpoint),
4912+ G_MOUNT_MOUNT_NONE,
4913+ mount_op,
4914+ db->priv->exiting,
4915+ (GAsyncReadyCallback) perform_next_mount_cb,
4916+ db);
4917+}
4918+
4919+/**
4920+ * rhythmdb_start_action_thread:
4921+ * @db: the #RhythmDB
4922+ *
4923+ * Starts the #RhythmDB processing thread. Needs to be called during startup.
4924+ */
4925+void
4926+rhythmdb_start_action_thread (RhythmDB *db)
4927+{
4928+ g_mutex_lock (db->priv->stat_mutex);
4929+ db->priv->action_thread_running = TRUE;
4930+ rhythmdb_thread_create (db, NULL, (GThreadFunc) action_thread_main, db);
4931+
4932+ if (db->priv->stat_list != NULL) {
4933+ RhythmDBStatThreadData *data;
4934+ data = g_new0 (RhythmDBStatThreadData, 1);
4935+ data->db = g_object_ref (db);
4936+ data->stat_list = db->priv->stat_list;
4937+ db->priv->stat_list = NULL;
4938+
4939+ db->priv->stat_thread_running = TRUE;
4940+ rhythmdb_thread_create (db, NULL, (GThreadFunc) stat_thread_main, data);
4941+ }
4942+
4943+ perform_next_mount (db);
4944+
4945+ g_mutex_unlock (db->priv->stat_mutex);
4946+}
4947+
4948+static void
4949+rhythmdb_action_free (RhythmDB *db,
4950+ RhythmDBAction *action)
4951+{
4952+ rb_refstring_unref (action->uri);
4953+ if (action->type == RHYTHMDB_ACTION_SYNC) {
4954+ free_entry_changes (action->data.changes);
4955+ }
4956+ g_slice_free (RhythmDBAction, action);
4957+}
4958+
4959+static void
4960+rhythmdb_event_free (RhythmDB *db,
4961+ RhythmDBEvent *result)
4962+{
4963+ switch (result->type) {
4964+ case RHYTHMDB_EVENT_THREAD_EXITED:
4965+ g_object_unref (db);
4966+ g_assert (g_atomic_int_dec_and_test (&db->priv->outstanding_threads) >= 0);
4967+ g_async_queue_unref (db->priv->action_queue);
4968+ g_async_queue_unref (db->priv->event_queue);
4969+ break;
4970+ case RHYTHMDB_EVENT_STAT:
4971+ case RHYTHMDB_EVENT_METADATA_LOAD:
4972+ case RHYTHMDB_EVENT_DB_LOAD:
4973+ case RHYTHMDB_EVENT_DB_SAVED:
4974+ case RHYTHMDB_EVENT_QUERY_COMPLETE:
4975+ break;
4976+ case RHYTHMDB_EVENT_ENTRY_SET:
4977+ g_value_unset (&result->change.new);
4978+ break;
4979+ }
4980+ if (result->error)
4981+ g_error_free (result->error);
4982+ rb_refstring_unref (result->uri);
4983+ rb_refstring_unref (result->real_uri);
4984+ if (result->file_info)
4985+ g_object_unref (result->file_info);
4986+ if (result->metadata)
4987+ g_object_unref (result->metadata);
4988+ if (result->results)
4989+ g_object_unref (result->results);
4990+ if (result->entry != NULL) {
4991+ rhythmdb_entry_unref (result->entry);
4992+ }
4993+ g_slice_free (RhythmDBEvent, result);
4994+}
4995+
4996+static void
4997+_shutdown_foreach_swapped (RhythmDBEvent *event, RhythmDB *db)
4998+{
4999+ rhythmdb_event_free (db, event);
5000+}
The diff has been truncated for viewing.

Subscribers

People subscribed via source and target branches