hythmbox-2.98/shell/rb-play-order-shuffle.c

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 }