No issues found
1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*-
2 *
3 * Copyright (C) 2003 Jeffrey Yasskin <jyasskin@mail.utexas.edu>
4 *
5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation; either version 2 of the License, or
8 * (at your option) any later version.
9 *
10 * The Rhythmbox authors hereby grant permission for non-GPL compatible
11 * GStreamer plugins to be used and distributed together with GStreamer
12 * and Rhythmbox. This permission is above and beyond the permissions granted
13 * by the GPL license by which Rhythmbox is covered. If you modify this code
14 * you may extend this exception to your version of the code, but you are not
15 * obligated to do so. If you do not wish to do so, delete this exception
16 * statement from your version.
17 *
18 * This program is distributed in the hope that it will be useful,
19 * but WITHOUT ANY WARRANTY; without even the implied warranty of
20 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 * GNU General Public License for more details.
22 *
23 * You should have received a copy of the GNU General Public License
24 * along with this program; if not, write to the Free Software
25 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
26 *
27 */
28
29 #include "config.h"
30
31 #include <string.h>
32
33 #include "rb-play-order-shuffle.h"
34
35 #include "rb-history.h"
36 #include "rb-debug.h"
37 #include "rb-util.h"
38
39 static void rb_shuffle_play_order_class_init (RBShufflePlayOrderClass *klass);
40 static void rb_shuffle_play_order_init (RBShufflePlayOrder *sorder);
41 static void rb_shuffle_play_order_dispose (GObject *object);
42 static void rb_shuffle_play_order_finalize (GObject *object);
43
44 static RhythmDBEntry* rb_shuffle_play_order_get_next (RBPlayOrder* method);
45 static void rb_shuffle_play_order_go_next (RBPlayOrder* method);
46 static RhythmDBEntry* rb_shuffle_play_order_get_previous (RBPlayOrder* method);
47 static void rb_shuffle_play_order_go_previous (RBPlayOrder* method);
48
49 static void rb_shuffle_sync_history_with_query_model (RBShufflePlayOrder *sorder);
50 static GPtrArray *get_query_model_contents (RhythmDBQueryModel *model);
51
52 static void rb_shuffle_db_changed (RBPlayOrder *porder, RhythmDB *db);
53 static void rb_shuffle_playing_entry_changed (RBPlayOrder *porder,
54 RhythmDBEntry *old_entry,
55 RhythmDBEntry *new_entry);
56 static void rb_shuffle_entry_added (RBPlayOrder *porder, RhythmDBEntry *entry);
57 static void rb_shuffle_entry_removed (RBPlayOrder *porder, RhythmDBEntry *entry);
58 static void rb_shuffle_query_model_changed (RBPlayOrder *porder);
59 static void rb_shuffle_db_entry_deleted (RBPlayOrder *porder, RhythmDBEntry *entry);
60 static gboolean query_model_and_history_contents_match (RBShufflePlayOrder *sorder);
61
62 struct RBShufflePlayOrderPrivate
63 {
64 RBHistory *history;
65
66 /** TRUE if the query model has been changed */
67 gboolean query_model_changed;
68
69 GHashTable *entries_removed;
70 GHashTable *entries_added;
71
72 /* stores the playing entry if it comes from outside the query model */
73 RhythmDBEntry *external_playing_entry;
74 };
75
76 G_DEFINE_TYPE (RBShufflePlayOrder, rb_shuffle_play_order, RB_TYPE_PLAY_ORDER)
77 #define RB_SHUFFLE_PLAY_ORDER_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), RB_TYPE_SHUFFLE_PLAY_ORDER, RBShufflePlayOrderPrivate))
78
79 RBPlayOrder *
80 rb_shuffle_play_order_new (RBShellPlayer *player)
81 {
82 RBShufflePlayOrder *sorder;
83
84 sorder = g_object_new (RB_TYPE_SHUFFLE_PLAY_ORDER,
85 "player", player,
86 NULL);
87
88 return RB_PLAY_ORDER (sorder);
89 }
90
91 static void
92 rb_shuffle_play_order_class_init (RBShufflePlayOrderClass *klass)
93 {
94 RBPlayOrderClass *porder;
95 GObjectClass *object_class = G_OBJECT_CLASS (klass);
96
97 object_class->dispose = rb_shuffle_play_order_dispose;
98 object_class->finalize = rb_shuffle_play_order_finalize;
99
100 porder = RB_PLAY_ORDER_CLASS (klass);
101
102 porder->db_changed = rb_shuffle_db_changed;
103 porder->playing_entry_changed = rb_shuffle_playing_entry_changed;
104 porder->entry_added = rb_shuffle_entry_added;
105 porder->entry_removed = rb_shuffle_entry_removed;
106 porder->query_model_changed = rb_shuffle_query_model_changed;
107 porder->db_entry_deleted = rb_shuffle_db_entry_deleted;
108
109 porder->has_next = rb_play_order_model_not_empty;
110 porder->has_previous = rb_play_order_model_not_empty;
111 porder->get_next = rb_shuffle_play_order_get_next;
112 porder->go_next = rb_shuffle_play_order_go_next;
113 porder->get_previous = rb_shuffle_play_order_get_previous;
114 porder->go_previous = rb_shuffle_play_order_go_previous;
115
116 g_type_class_add_private (klass, sizeof (RBShufflePlayOrderPrivate));
117 }
118
119 static void
120 rb_shuffle_play_order_init (RBShufflePlayOrder *sorder)
121 {
122 sorder->priv = RB_SHUFFLE_PLAY_ORDER_GET_PRIVATE (sorder);
123
124 sorder->priv->history = rb_history_new (FALSE,
125 (GFunc) rhythmdb_entry_unref,
126 NULL);
127
128 sorder->priv->query_model_changed = FALSE;
129 sorder->priv->entries_added = g_hash_table_new_full (g_direct_hash, g_direct_equal,
130 (GDestroyNotify)rhythmdb_entry_unref, NULL);
131 sorder->priv->entries_removed = g_hash_table_new_full (g_direct_hash, g_direct_equal,
132 (GDestroyNotify)rhythmdb_entry_unref, NULL);
133 }
134
135 static void
136 rb_shuffle_play_order_dispose (GObject *object)
137 {
138 RBShufflePlayOrder *sorder;
139
140 g_return_if_fail (object != NULL);
141 g_return_if_fail (RB_IS_SHUFFLE_PLAY_ORDER (object));
142
143 sorder = RB_SHUFFLE_PLAY_ORDER (object);
144
145 if (sorder->priv->external_playing_entry != NULL) {
146 rhythmdb_entry_unref (sorder->priv->external_playing_entry);
147 sorder->priv->external_playing_entry = NULL;
148 }
149
150 if (sorder->priv->history != NULL) {
151 g_object_unref (sorder->priv->history);
152 sorder->priv->history = NULL;
153 }
154
155 G_OBJECT_CLASS (rb_shuffle_play_order_parent_class)->dispose (object);
156 }
157
158 static void
159 rb_shuffle_play_order_finalize (GObject *object)
160 {
161 RBShufflePlayOrder *sorder;
162
163 g_return_if_fail (object != NULL);
164 g_return_if_fail (RB_IS_SHUFFLE_PLAY_ORDER (object));
165
166 sorder = RB_SHUFFLE_PLAY_ORDER (object);
167
168 g_hash_table_destroy (sorder->priv->entries_added);
169 g_hash_table_destroy (sorder->priv->entries_removed);
170
171 G_OBJECT_CLASS (rb_shuffle_play_order_parent_class)->finalize (object);
172 }
173
174 static RhythmDBEntry*
175 rb_shuffle_play_order_get_next (RBPlayOrder* porder)
176 {
177 RBShufflePlayOrder *sorder;
178 RhythmDBEntry *entry;
179 RhythmDBEntry *current;
180
181 g_return_val_if_fail (porder != NULL, NULL);
182 g_return_val_if_fail (RB_IS_SHUFFLE_PLAY_ORDER (porder), NULL);
183
184 sorder = RB_SHUFFLE_PLAY_ORDER (porder);
185
186 rb_shuffle_sync_history_with_query_model (sorder);
187
188 current = rb_play_order_get_playing_entry (porder);
189 entry = NULL;
190
191 if (current != NULL &&
192 (current == sorder->priv->external_playing_entry ||
193 current == rb_history_current (sorder->priv->history))) {
194 if (rb_history_current (sorder->priv->history) != rb_history_last (sorder->priv->history)) {
195 rb_debug ("choosing next entry in shuffle");
196 entry = rb_history_next (sorder->priv->history);
197 if (entry)
198 rhythmdb_entry_ref (entry);
199 }
200 } else {
201 /* If the player is currently stopped, the "next" (first) song
202 * is the first in the shuffle. */
203 rb_debug ("choosing current entry in shuffle");
204 entry = rb_history_current (sorder->priv->history);
205
206 if (entry == NULL)
207 entry = rb_history_first (sorder->priv->history);
208
209 if (entry != NULL)
210 rhythmdb_entry_ref (entry);
211 }
212
213 if (current)
214 rhythmdb_entry_unref (current);
215 return entry;
216 }
217
218 static void
219 rb_shuffle_play_order_go_next (RBPlayOrder* porder)
220 {
221 RBShufflePlayOrder *sorder;
222 RhythmDBEntry *entry;
223
224 g_return_if_fail (porder != NULL);
225 g_return_if_fail (RB_IS_SHUFFLE_PLAY_ORDER (porder));
226
227 sorder = RB_SHUFFLE_PLAY_ORDER (porder);
228
229 entry = rb_play_order_get_playing_entry (porder);
230 g_assert (entry == NULL ||
231 rb_history_current (sorder->priv->history) == NULL ||
232 (entry == sorder->priv->external_playing_entry ||
233 entry == rb_history_current (sorder->priv->history)));
234
235 if (rb_history_current (sorder->priv->history) == NULL) {
236 rb_history_go_first (sorder->priv->history);
237 } else if (entry == rb_history_current (sorder->priv->history) ||
238 (sorder->priv->external_playing_entry != NULL &&
239 entry == sorder->priv->external_playing_entry)) {
240 if (rb_history_current (sorder->priv->history) != rb_history_last (sorder->priv->history))
241 rb_history_go_next (sorder->priv->history);
242 }
243
244 rb_play_order_set_playing_entry (porder, rb_history_current (sorder->priv->history));
245
246 if (entry)
247 rhythmdb_entry_unref (entry);
248 }
249
250 static RhythmDBEntry*
251 rb_shuffle_play_order_get_previous (RBPlayOrder* porder)
252 {
253 RBShufflePlayOrder *sorder;
254 RhythmDBEntry *entry;
255
256 g_return_val_if_fail (porder != NULL, NULL);
257 g_return_val_if_fail (RB_IS_SHUFFLE_PLAY_ORDER (porder), NULL);
258 /* It doesn't make sense to call get_previous when the player is stopped */
259 g_return_val_if_fail (rb_play_order_player_is_playing (porder), NULL);
260
261 sorder = RB_SHUFFLE_PLAY_ORDER (porder);
262
263 rb_shuffle_sync_history_with_query_model (sorder);
264
265 if (sorder->priv->external_playing_entry != NULL) {
266 rb_debug ("playing from outside the query model; previous is current");
267 entry = rb_history_current (sorder->priv->history);
268 } else {
269 rb_debug ("choosing previous history entry");
270 entry = rb_history_previous (sorder->priv->history);
271 }
272
273 if (entry)
274 rhythmdb_entry_ref (entry);
275
276 return entry;
277 }
278
279 static void
280 rb_shuffle_play_order_go_previous (RBPlayOrder* porder)
281 {
282 RBShufflePlayOrder *sorder;
283
284 g_return_if_fail (porder != NULL);
285 g_return_if_fail (RB_IS_SHUFFLE_PLAY_ORDER (porder));
286 /* It doesn't make sense to call go_previous when the player is stopped */
287 g_return_if_fail (rb_play_order_player_is_playing (porder));
288
289 sorder = RB_SHUFFLE_PLAY_ORDER (porder);
290
291 if (sorder->priv->external_playing_entry != NULL) {
292 /* if we were playing an external entry, the current entry
293 * is the history is the one before it.
294 */
295 rb_play_order_set_playing_entry (porder, rb_history_current (sorder->priv->history));
296
297 rhythmdb_entry_unref (sorder->priv->external_playing_entry);
298 sorder->priv->external_playing_entry = NULL;
299 } else {
300 if (rb_history_current (sorder->priv->history) != rb_history_first (sorder->priv->history)) {
301 rb_history_go_previous (sorder->priv->history);
302 rb_play_order_set_playing_entry (porder, rb_history_current (sorder->priv->history));
303 }
304 }
305 }
306
307 static void
308 handle_query_model_changed (RBShufflePlayOrder *sorder)
309 {
310 GPtrArray *history;
311 RhythmDBQueryModel *model;
312 GtkTreeIter iter;
313 int i;
314
315 if (!sorder->priv->query_model_changed)
316 return;
317
318 g_hash_table_foreach_remove (sorder->priv->entries_added, (GHRFunc) rb_true_function, NULL);
319 g_hash_table_foreach_remove (sorder->priv->entries_removed, (GHRFunc) rb_true_function, NULL);
320
321 /* This simulates removing every entry in the old query model
322 * and then adding every entry in the new one. */
323 history = rb_history_dump (sorder->priv->history);
324 for (i=0; i < history->len; ++i)
325 rb_shuffle_entry_removed (RB_PLAY_ORDER (sorder), g_ptr_array_index (history, i));
326 g_ptr_array_free (history, TRUE);
327
328 model = rb_play_order_get_query_model (RB_PLAY_ORDER (sorder));
329 if (gtk_tree_model_get_iter_first (GTK_TREE_MODEL (model), &iter)) {
330 do {
331 RhythmDBEntry *entry;
332 entry = rhythmdb_query_model_iter_to_entry (model, &iter);
333 rb_shuffle_entry_added (RB_PLAY_ORDER (sorder), entry);
334 rhythmdb_entry_unref (entry);
335 } while (gtk_tree_model_iter_next (GTK_TREE_MODEL (model), &iter));
336 }
337
338 sorder->priv->query_model_changed = FALSE;
339 }
340
341 static gboolean
342 remove_from_history (RhythmDBEntry *entry, gpointer *unused, RBShufflePlayOrder *sorder)
343 {
344 if (rb_history_contains_entry (sorder->priv->history, entry)) {
345 rb_history_remove_entry (sorder->priv->history, entry);
346 }
347 return TRUE;
348 }
349
350 static gboolean
351 add_randomly_to_history (RhythmDBEntry *entry, gpointer *unused, RBShufflePlayOrder *sorder)
352 {
353 gint history_size;
354 gint current_index;
355
356 if (rb_history_contains_entry (sorder->priv->history, entry))
357 return TRUE;
358
359 history_size = rb_history_length (sorder->priv->history);
360 current_index = rb_history_get_current_index (sorder->priv->history);
361 /* Insert entry into the history at a random position between
362 * just after current and the very end. */
363 rb_history_insert_at_index (sorder->priv->history,
364 rhythmdb_entry_ref (entry),
365 g_random_int_range (MIN (current_index, history_size-1) + 1,
366 history_size + 1));
367 return TRUE;
368 }
369
370 static void
371 rb_shuffle_sync_history_with_query_model (RBShufflePlayOrder *sorder)
372 {
373 RhythmDBEntry *current = rb_history_current (sorder->priv->history);
374
375 handle_query_model_changed (sorder);
376 g_hash_table_foreach_remove (sorder->priv->entries_removed, (GHRFunc) remove_from_history, sorder);
377 g_hash_table_foreach_remove (sorder->priv->entries_added, (GHRFunc) add_randomly_to_history, sorder);
378
379 if (sorder->priv->external_playing_entry != NULL) {
380 if (rb_history_contains_entry (sorder->priv->history,
381 sorder->priv->external_playing_entry)) {
382 /* history now contains the previously external entry, so
383 * use it as the playing entry.
384 */
385 rb_history_set_playing (sorder->priv->history,
386 sorder->priv->external_playing_entry);
387 rhythmdb_entry_unref (sorder->priv->external_playing_entry);
388 sorder->priv->external_playing_entry = NULL;
389 current = NULL;
390 }
391 }
392
393 if (current != NULL) {
394 /* if the current entry no longer exists in the history, go back to the start */
395 if (!rb_history_contains_entry (sorder->priv->history, current)) {
396 rb_history_set_playing (sorder->priv->history, NULL);
397 }
398 }
399
400 /* postconditions */
401 g_assert (query_model_and_history_contents_match (sorder));
402 g_assert (g_hash_table_size (sorder->priv->entries_added) == 0);
403 g_assert (g_hash_table_size (sorder->priv->entries_removed) == 0);
404 }
405
406 /* NOTE: returned GPtrArray does not hold references to the entries */
407 static GPtrArray *
408 get_query_model_contents (RhythmDBQueryModel *model)
409 {
410 guint num_entries;
411 guint i = 0;
412 GtkTreeIter iter;
413
414 GPtrArray *result = g_ptr_array_new ();
415 if (model == NULL)
416 return result;
417
418 num_entries = gtk_tree_model_iter_n_children (GTK_TREE_MODEL (model), NULL);
419 if (num_entries == 0)
420 return result;
421
422 g_ptr_array_set_size (result, num_entries);
423
424 if (!gtk_tree_model_get_iter_first (GTK_TREE_MODEL (model), &iter))
425 return result;
426 do {
427 RhythmDBEntry *entry;
428 entry = rhythmdb_query_model_iter_to_entry (model, &iter);
429 g_ptr_array_index (result, i++) = entry;
430 rhythmdb_entry_unref (entry);
431 } while (gtk_tree_model_iter_next (GTK_TREE_MODEL (model), &iter));
432
433 return result;
434 }
435
436 static void
437 rb_shuffle_db_changed (RBPlayOrder *porder, RhythmDB *db)
438 {
439 g_return_if_fail (RB_IS_SHUFFLE_PLAY_ORDER (porder));
440
441 rb_history_clear (RB_SHUFFLE_PLAY_ORDER (porder)->priv->history);
442 }
443
444 static void
445 rb_shuffle_playing_entry_changed (RBPlayOrder *porder,
446 RhythmDBEntry *old_entry,
447 RhythmDBEntry *new_entry)
448 {
449 RBShufflePlayOrder *sorder;
450
451 g_return_if_fail (RB_IS_SHUFFLE_PLAY_ORDER (porder));
452 sorder = RB_SHUFFLE_PLAY_ORDER (porder);
453
454 if (sorder->priv->external_playing_entry != NULL) {
455 rhythmdb_entry_unref (sorder->priv->external_playing_entry);
456 sorder->priv->external_playing_entry = NULL;
457 }
458
459 if (new_entry) {
460 if (new_entry == rb_history_current (sorder->priv->history)) {
461 /* Do nothing */
462 } else if (rb_history_contains_entry (sorder->priv->history, new_entry)) {
463 rhythmdb_entry_ref (new_entry);
464 rb_history_set_playing (sorder->priv->history, new_entry);
465 } else {
466 /* playing an entry outside the query model;
467 * track the entry separately as if it was between
468 * the current entry in the history and the next.
469 */
470 rhythmdb_entry_ref (new_entry);
471 sorder->priv->external_playing_entry = new_entry;
472 }
473 } else {
474 /* go back to the start if we just finished the play order */
475 if (old_entry == rb_history_last (sorder->priv->history))
476 rb_history_go_first (sorder->priv->history);
477 }
478 }
479
480 static void
481 rb_shuffle_entry_added (RBPlayOrder *porder, RhythmDBEntry *entry)
482 {
483 g_return_if_fail (RB_IS_SHUFFLE_PLAY_ORDER (porder));
484 g_hash_table_remove (RB_SHUFFLE_PLAY_ORDER (porder)->priv->entries_removed, entry);
485 g_hash_table_insert (RB_SHUFFLE_PLAY_ORDER (porder)->priv->entries_added, rhythmdb_entry_ref (entry), entry);
486 }
487
488 static void
489 rb_shuffle_entry_removed (RBPlayOrder *porder, RhythmDBEntry *entry)
490 {
491 g_return_if_fail (RB_IS_SHUFFLE_PLAY_ORDER (porder));
492 g_hash_table_remove (RB_SHUFFLE_PLAY_ORDER (porder)->priv->entries_added, entry);
493 g_hash_table_insert (RB_SHUFFLE_PLAY_ORDER (porder)->priv->entries_removed, rhythmdb_entry_ref (entry), entry);
494 }
495
496 static void
497 rb_shuffle_query_model_changed (RBPlayOrder *porder)
498 {
499 g_return_if_fail (RB_IS_SHUFFLE_PLAY_ORDER (porder));
500 RB_SHUFFLE_PLAY_ORDER (porder)->priv->query_model_changed = TRUE;
501 }
502
503 static void
504 rb_shuffle_db_entry_deleted (RBPlayOrder *porder, RhythmDBEntry *entry)
505 {
506 RBShufflePlayOrder *sorder;
507
508 g_return_if_fail (RB_IS_SHUFFLE_PLAY_ORDER (porder));
509 sorder = RB_SHUFFLE_PLAY_ORDER (porder);
510
511 rb_history_remove_entry (sorder->priv->history, entry);
512 }
513
514 /* For some reason g_ptr_array_sort() passes pointers to the array elements
515 * rather than the elements themselves */
516 static gint
517 ptr_compare (gconstpointer a, gconstpointer b)
518 {
519 if (*(gconstpointer*)a < *(gconstpointer*)b)
520 return -1;
521 if (*(gconstpointer*)b < *(gconstpointer*)a)
522 return 1;
523 return 0;
524 }
525
526 static gboolean
527 query_model_and_history_contents_match (RBShufflePlayOrder *sorder)
528 {
529 gboolean result = TRUE;
530 GPtrArray *history_contents;
531 GPtrArray *query_model_contents;
532
533 history_contents = rb_history_dump (sorder->priv->history);
534 query_model_contents = get_query_model_contents (rb_play_order_get_query_model (RB_PLAY_ORDER (sorder)));
535
536 if (history_contents->len != query_model_contents->len)
537 result = FALSE;
538 else {
539 int i;
540 g_ptr_array_sort (history_contents, ptr_compare);
541 g_ptr_array_sort (query_model_contents, ptr_compare);
542 for (i=0; i<history_contents->len; ++i) {
543 if (g_ptr_array_index (history_contents, i) != g_ptr_array_index (query_model_contents, i)) {
544 result = FALSE;
545 break;
546 }
547 }
548 }
549 g_ptr_array_free (history_contents, TRUE);
550 g_ptr_array_free (query_model_contents, TRUE);
551 return result;
552 }