hythmbox-2.98/shell/rb-track-transfer-batch.c

No issues found

   1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*-
   2  *
   3  *  Copyright (C) 2010  Jonathan Matthew  <jonathan@d14n.org>
   4  *
   5  *  This program is free software; you can redistribute it and/or modify
   6  *  it under the terms of the GNU General Public License as published by
   7  *  the Free Software Foundation; either version 2 of the License, or
   8  *  (at your option) any later version.
   9  *
  10  *  The Rhythmbox authors hereby grants permission for non-GPL compatible
  11  *  GStreamer plugins to be used and distributed together with GStreamer
  12  *  and Rhythmbox. This permission is above and beyond the permissions granted
  13  *  by the GPL license by which Rhythmbox is covered. If you modify this code
  14  *  you may extend this exception to your version of the code, but you are not
  15  *  obligated to do so. If you do not wish to do so, delete this exception
  16  *  statement from your version.
  17  *
  18  *  This program is distributed in the hope that it will be useful,
  19  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
  20  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  21  *  GNU General Public License for more details.
  22  *
  23  *  You should have received a copy of the GNU General Public License
  24  *  along with this program; if not, write to the Free Software
  25  *  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301  USA.
  26  *
  27  */
  28 
  29 #include "config.h"
  30 
  31 #include <glib/gi18n.h>
  32 
  33 #include <gst/pbutils/install-plugins.h>
  34 
  35 #include "rb-source.h"
  36 #include "rb-track-transfer-batch.h"
  37 #include "rb-track-transfer-queue.h"
  38 #include "rb-encoder.h"
  39 #include "rb-marshal.h"
  40 #include "rb-debug.h"
  41 #include "rb-util.h"
  42 #include "rb-gst-media-types.h"
  43 
  44 enum
  45 {
  46 	STARTED,
  47 	COMPLETE,
  48 	CANCELLED,
  49 	GET_DEST_URI,
  50 	OVERWRITE_PROMPT,
  51 	TRACK_STARTED,
  52 	TRACK_PROGRESS,
  53 	TRACK_DONE,
  54 	CONFIGURE_PROFILE,
  55 	LAST_SIGNAL
  56 };
  57 
  58 enum
  59 {
  60 	PROP_0,
  61 	PROP_ENCODING_TARGET,
  62 	PROP_SOURCE,
  63 	PROP_DESTINATION,
  64 	PROP_TOTAL_ENTRIES,
  65 	PROP_DONE_ENTRIES,
  66 	PROP_PROGRESS,
  67 	PROP_ENTRY_LIST
  68 };
  69 
  70 static void	rb_track_transfer_batch_class_init (RBTrackTransferBatchClass *klass);
  71 static void	rb_track_transfer_batch_init (RBTrackTransferBatch *batch);
  72 
  73 static gboolean start_next (RBTrackTransferBatch *batch);
  74 static void start_encoding (RBTrackTransferBatch *batch, gboolean overwrite);
  75 static void track_transfer_completed (RBTrackTransferBatch *batch,
  76 				      guint64 dest_size,
  77 				      const char *mediatype,
  78 				      gboolean skipped,
  79 				      GError *error);
  80 
  81 static guint	signals[LAST_SIGNAL] = { 0 };
  82 
  83 struct _RBTrackTransferBatchPrivate
  84 {
  85 	RBTrackTransferQueue *queue;
  86 
  87 	GstEncodingTarget *target;
  88 	GList *missing_plugin_profiles;
  89 
  90 	RBSource *source;
  91 	RBSource *destination;
  92 
  93 	GList *entries;
  94 	GList *done_entries;
  95 
  96 	guint64 total_duration;
  97 	guint64 total_size;
  98 	double total_fraction;
  99 
 100 	RhythmDBEntry *current;
 101 	double current_entry_fraction;
 102 	char *current_dest_uri;
 103 	double current_fraction;
 104 	RBEncoder *current_encoder;
 105 	GstEncodingProfile *current_profile;
 106 	gboolean cancelled;
 107 };
 108 
 109 G_DEFINE_TYPE (RBTrackTransferBatch, rb_track_transfer_batch, G_TYPE_OBJECT)
 110 
 111 /**
 112  * SECTION:rb-track-transfer-batch
 113  * @short_description: batch track transfer job
 114  *
 115  * Manages the transfer of a set of tracks (using #RBEncoder), providing overall
 116  * status information and allowing the transfer to be cancelled as a single unit.
 117  */
 118 
 119 /**
 120  * rb_track_transfer_batch_new:
 121  * @target: a #GstEncodingTarget describing allowable encodings (or NULL for defaults)
 122  * @source: the #RBSource from which the entries are to be transferred
 123  * @destination: the #RBSource to which the entries are to be transferred
 124  *
 125  * Creates a new transfer batch with the specified encoding target.  If no target
 126  * is specified, the default target will be used with the user's preferred
 127  * encoding type.
 128  *
 129  * One or more entries must be added to the batch (using #rb_track_transfer_batch_add)
 130  * before the batch can be started (#rb_track_transfer_manager_start_batch).
 131  *
 132  * Return value: new #RBTrackTransferBatch object
 133  */
 134 RBTrackTransferBatch *
 135 rb_track_transfer_batch_new (GstEncodingTarget *target,
 136 			     GObject *source,
 137 			     GObject *destination)
 138 {
 139 	GObject *obj;
 140 
 141 	obj = g_object_new (RB_TYPE_TRACK_TRANSFER_BATCH,
 142 			    "encoding-target", target,
 143 			    "source", source,
 144 			    "destination", destination,
 145 			    NULL);
 146 	return RB_TRACK_TRANSFER_BATCH (obj);
 147 }
 148 
 149 /**
 150  * rb_track_transfer_batch_add:
 151  * @batch: a #RBTrackTransferBatch
 152  * @entry: the source #RhythmDBEntry to transfer
 153  *
 154  * Adds an entry to be transferred.
 155  */
 156 void
 157 rb_track_transfer_batch_add (RBTrackTransferBatch *batch, RhythmDBEntry *entry)
 158 {
 159 	batch->priv->entries = g_list_append (batch->priv->entries, rhythmdb_entry_ref (entry));
 160 }
 161 
 162 static gboolean
 163 select_profile_for_entry (RBTrackTransferBatch *batch, RhythmDBEntry *entry, GstEncodingProfile **rprofile, gboolean allow_missing)
 164 {
 165 	/* probably want a way to pass in some policy about lossless encoding
 166 	 * here.  possibilities:
 167 	 * - convert everything to lossy
 168 	 * - if transcoding is required, use lossy
 169 	 * - keep lossless encoded files lossless
 170 	 * - if transcoding is required, use lossless
 171 	 * - convert everything to lossless
 172 	 *
 173 	 * of course this only applies to targets that include lossless profiles..
 174 	 */
 175 
 176 	const char *media_type = rhythmdb_entry_get_string (entry, RHYTHMDB_PROP_MEDIA_TYPE);
 177 	GstEncodingProfile *lossless = NULL;
 178 	gboolean found_lossy = FALSE;
 179 	const GList *p;
 180 
 181 	for (p = gst_encoding_target_get_profiles (batch->priv->target); p != NULL; p = p->next) {
 182 		GstEncodingProfile *profile = GST_ENCODING_PROFILE (p->data);
 183 		char *profile_media_type;
 184 		gboolean is_missing;
 185 		gboolean skip;
 186 
 187 		if (g_str_has_prefix (media_type, "audio/x-raw") == FALSE &&
 188 		    rb_gst_media_type_matches_profile (profile, media_type)) {
 189 			/* source file is already in a supported encoding, so just copy it */
 190 			*rprofile = NULL;
 191 			return TRUE;
 192 		}
 193 
 194 		skip = FALSE;
 195 		is_missing = (g_list_find (batch->priv->missing_plugin_profiles, profile) != NULL);
 196 
 197 		profile_media_type = rb_gst_encoding_profile_get_media_type (profile);
 198 		if (profile_media_type == NULL) {
 199 			if (g_str_has_prefix (media_type, "audio/x-raw")) {
 200 				skip = TRUE;
 201 			}
 202 		} else if (rb_gst_media_type_is_lossless (profile_media_type)) {
 203 			skip = TRUE;
 204 			if (allow_missing == FALSE && is_missing) {
 205 				/* ignore entirely */
 206 			} else if (lossless == NULL) {
 207 				/* remember the first lossless profile that works */
 208 				lossless = profile;
 209 			}
 210 		} else {
 211 			found_lossy = TRUE;
 212 			if (allow_missing == FALSE && is_missing) {
 213 				skip = TRUE;
 214 			}
 215 		}
 216 
 217 		if (skip == FALSE && *rprofile == NULL) {
 218 			*rprofile = profile;
 219 		}
 220 		g_free (profile_media_type);
 221 	}
 222 
 223 	/* if we only found a lossless encoding, use it */
 224 	if (*rprofile == NULL && found_lossy == FALSE && lossless != NULL) {
 225 		*rprofile = lossless;
 226 	}
 227 
 228 	return (*rprofile != NULL);
 229 }
 230 
 231 /**
 232  * rb_track_transfer_batch_check_profiles:
 233  * @batch: a #RBTrackTransferBatch
 234  * @missing_plugin_profiles: holds a #GList of #GstEncodingProfiles on return
 235  * @error_count: holds the number of entries that cannot be transferred on return
 236  *
 237  * Checks that all entries in the batch can be transferred in a format
 238  * supported by the destination.  If no encoding profile is available for
 239  * some entries, but installing additional plugins could make a profile
 240  * available, a list of profiles that require additional plugins is returned.
 241  *
 242  * Return value: %TRUE if some entries can be transferred without additional plugins
 243  */
 244 gboolean
 245 rb_track_transfer_batch_check_profiles (RBTrackTransferBatch *batch, GList **missing_plugin_profiles, int *error_count)
 246 {
 247 	RBEncoder *encoder = rb_encoder_new ();
 248 	gboolean ret = FALSE;
 249 	const GList *l;
 250 
 251 	rb_debug ("checking profiles");
 252 
 253 	/* first, figure out which profiles that we care about would require additional plugins to use */
 254 	g_list_free (batch->priv->missing_plugin_profiles);
 255 	batch->priv->missing_plugin_profiles = NULL;
 256 
 257 	for (l = gst_encoding_target_get_profiles (batch->priv->target); l != NULL; l = l->next) {
 258 		GstEncodingProfile *profile = GST_ENCODING_PROFILE (l->data);
 259 		char *profile_media_type;
 260 		profile_media_type = rb_gst_encoding_profile_get_media_type (profile);
 261 		if (profile_media_type != NULL &&
 262 		    (rb_gst_media_type_is_lossless (profile_media_type) == FALSE) &&
 263 		    rb_encoder_get_missing_plugins (encoder, profile, NULL, NULL)) {
 264 			batch->priv->missing_plugin_profiles = g_list_append (batch->priv->missing_plugin_profiles, profile);
 265 		}
 266 		g_free (profile_media_type);
 267 	}
 268 	g_object_unref (encoder);
 269 
 270 	rb_debug ("have %d profiles with missing plugins", g_list_length (batch->priv->missing_plugin_profiles));
 271 
 272 	for (l = batch->priv->entries; l != NULL; l = l->next) {
 273 		RhythmDBEntry *entry = (RhythmDBEntry *)l->data;
 274 		GstEncodingProfile *profile;
 275 
 276 		profile = NULL;
 277 		if (select_profile_for_entry (batch, entry, &profile, FALSE) == TRUE) {
 278 			if (profile != NULL) {
 279 				rb_debug ("found profile %s for %s",
 280 					  gst_encoding_profile_get_name (profile),
 281 					  rhythmdb_entry_get_string (entry, RHYTHMDB_PROP_LOCATION));
 282 			} else {
 283 				rb_debug ("copying entry %s", rhythmdb_entry_get_string (entry, RHYTHMDB_PROP_LOCATION));
 284 			}
 285 			ret = TRUE;
 286 			continue;
 287 		}
 288 
 289 		(*error_count)++;
 290 		if (select_profile_for_entry (batch, entry, &profile, TRUE) == FALSE) {
 291 			rb_debug ("unable to transfer %s (media type %s)",
 292 				  rhythmdb_entry_get_string (entry, RHYTHMDB_PROP_LOCATION),
 293 				  rhythmdb_entry_get_string (entry, RHYTHMDB_PROP_MEDIA_TYPE));
 294 		} else {
 295 			rb_debug ("require additional plugins to transfer %s (media type %s)",
 296 				  rhythmdb_entry_get_string (entry, RHYTHMDB_PROP_LOCATION),
 297 				  rhythmdb_entry_get_string (entry, RHYTHMDB_PROP_MEDIA_TYPE));
 298 			if (*missing_plugin_profiles == NULL) {
 299 				*missing_plugin_profiles = g_list_copy (batch->priv->missing_plugin_profiles);
 300 			}
 301 		}
 302 	}
 303 	return ret;
 304 }
 305 
 306 /**
 307  * rb_track_transfer_batch_cancel:
 308  * @batch: a #RBTrackTransferBatch
 309  *
 310  * Cancels the batch.
 311  */
 312 void
 313 rb_track_transfer_batch_cancel (RBTrackTransferBatch *batch)
 314 {
 315 	rb_track_transfer_queue_cancel_batch (batch->priv->queue, batch);
 316 }
 317 
 318 /**
 319  * _rb_track_transfer_batch_start:
 320  * @batch: a #RBTrackTransferBatch
 321  * @queue: the #RBTrackTransferQueue
 322  *
 323  * Starts the batch transfer.  Only to be called by the #RBTrackTransferQueue.
 324  */
 325 void
 326 _rb_track_transfer_batch_start (RBTrackTransferBatch *batch, GObject *queue)
 327 {
 328 	gboolean total_duration_valid;
 329 	gboolean total_size_valid;
 330 	gboolean origin_valid;
 331 	guint64 filesize;
 332 	gulong duration;
 333 	RBSource *origin = NULL;
 334 	RBShell *shell;
 335 	GList *l;
 336 
 337 	g_object_get (queue, "shell", &shell, NULL);
 338 
 339 	/* calculate total duration and file size and figure out the
 340 	 * origin source if we weren't given one to start with.
 341 	 */
 342 	total_duration_valid = TRUE;
 343 	total_size_valid = TRUE;
 344 	origin_valid = TRUE;
 345 	for (l = batch->priv->entries; l != NULL; l = l->next) {
 346 		RhythmDBEntry *entry = (RhythmDBEntry *)l->data;
 347 
 348 		filesize = rhythmdb_entry_get_uint64 (entry, RHYTHMDB_PROP_FILE_SIZE);
 349 		if (total_size_valid && filesize > 0) {
 350 			batch->priv->total_size += filesize;
 351 		} else {
 352 			total_size_valid = FALSE;
 353 			batch->priv->total_size = 0;
 354 		}
 355 
 356 		duration = rhythmdb_entry_get_ulong (entry, RHYTHMDB_PROP_DURATION);
 357 		if (total_duration_valid && duration > 0) {
 358 			batch->priv->total_duration += duration;
 359 		} else {
 360 			total_duration_valid = FALSE;
 361 			batch->priv->total_duration = 0;
 362 		}
 363 
 364 		if (batch->priv->source == NULL) {
 365 			RhythmDBEntryType *entry_type;
 366 			RBSource *entry_origin;
 367 
 368 			entry_type = rhythmdb_entry_get_entry_type (entry);
 369 			entry_origin = rb_shell_get_source_by_entry_type (shell, entry_type);
 370 			if (origin == NULL && origin_valid  == TRUE) {
 371 				origin = entry_origin;
 372 			} else if (origin != entry_origin) {
 373 				origin = NULL;
 374 				origin_valid = FALSE;
 375 			}
 376 		}
 377 	}
 378 
 379 	g_object_unref (shell);
 380 
 381 	if (origin != NULL) {
 382 		batch->priv->source = origin;
 383 	}
 384 
 385 	batch->priv->queue = RB_TRACK_TRANSFER_QUEUE (queue);
 386 	batch->priv->cancelled = FALSE;
 387 	batch->priv->total_fraction = 0.0;
 388 
 389 	g_signal_emit (batch, signals[STARTED], 0);
 390 
 391 	start_next (batch);
 392 }
 393 
 394 /**
 395  * _rb_track_transfer_batch_cancel:
 396  * @batch: a #RBTrackTransferBatch
 397  *
 398  * Cancels a batch transfer.  Only to be called by the #RBTrackTransferQueue.
 399  */
 400 void
 401 _rb_track_transfer_batch_cancel (RBTrackTransferBatch *batch)
 402 {
 403 	batch->priv->cancelled = TRUE;
 404 	rb_debug ("batch being cancelled");
 405 
 406 	if (batch->priv->current_encoder != NULL) {
 407 		rb_encoder_cancel (batch->priv->current_encoder);
 408 
 409 		/* other things take care of cleaning up the encoder */
 410 	}
 411 
 412 	g_signal_emit (batch, signals[CANCELLED], 0);
 413 
 414 	/* anything else? */
 415 }
 416 
 417 /**
 418  * _rb_track_transfer_batch_continue:
 419  * @batch: a #RBTrackTransferBatch
 420  * @overwrite: if %TRUE, overwrite the current file, otherwise skip
 421  *
 422  * Continues a transfer that was suspended because the current
 423  * destination URI exists.  Only to be called by the #RBTrackTransferQueue.
 424  */
 425 void
 426 _rb_track_transfer_batch_continue (RBTrackTransferBatch *batch, gboolean overwrite)
 427 {
 428 	if (overwrite) {
 429 		start_encoding (batch, TRUE);
 430 	} else {
 431 		track_transfer_completed (batch, 0, NULL, TRUE, NULL);
 432 	}
 433 }
 434 
 435 static void
 436 emit_progress (RBTrackTransferBatch *batch)
 437 {
 438 	int done;
 439 	int total;
 440 	double fraction;
 441 
 442 	g_object_get (batch,
 443 		      "total-entries", &total,
 444 		      "done-entries", &done,
 445 		      "progress", &fraction,
 446 		      NULL);
 447 	g_signal_emit (batch, signals[TRACK_PROGRESS], 0,
 448 		       batch->priv->current,
 449 		       batch->priv->current_dest_uri,
 450 		       done,
 451 		       total,
 452 		       fraction);
 453 }
 454 
 455 static void
 456 encoder_progress_cb (RBEncoder *encoder, double fraction, RBTrackTransferBatch *batch)
 457 {
 458 	batch->priv->current_fraction = fraction;
 459 	emit_progress (batch);
 460 }
 461 
 462 static void
 463 track_transfer_completed (RBTrackTransferBatch *batch,
 464 			  guint64 dest_size,
 465 			  const char *mediatype,
 466 			  gboolean skipped,
 467 			  GError *error)
 468 {
 469 	RhythmDBEntry *entry;
 470 
 471 	entry = batch->priv->current;
 472 	batch->priv->current = NULL;
 473 
 474 	batch->priv->current_profile = NULL;
 475 
 476 	/* update batch state to reflect that the track is done */
 477 	batch->priv->total_fraction += batch->priv->current_entry_fraction;
 478 	batch->priv->done_entries = g_list_append (batch->priv->done_entries, entry);
 479 
 480 	if (batch->priv->cancelled == FALSE) {
 481 		/* keep ourselves alive until the end of the function, since it's
 482 		 * possible that a signal handler will cancel us.
 483 		 */
 484 		g_object_ref (batch);
 485 		if (skipped == FALSE) {
 486 			g_signal_emit (batch, signals[TRACK_DONE], 0,
 487 				       entry,
 488 				       batch->priv->current_dest_uri,
 489 				       dest_size,
 490 				       mediatype,
 491 				       error);
 492 		}
 493 
 494 		start_next (batch);
 495 
 496 		g_object_unref (batch);
 497 	}
 498 }
 499 
 500 static void
 501 encoder_completed_cb (RBEncoder *encoder,
 502 		      guint64 dest_size,
 503 		      const char *mediatype,
 504 		      GError *error,
 505 		      RBTrackTransferBatch *batch)
 506 {
 507 	g_object_unref (batch->priv->current_encoder);
 508 	batch->priv->current_encoder = NULL;
 509 
 510 	if (error == NULL) {
 511 		rb_debug ("encoder finished (size %" G_GUINT64_FORMAT ")", dest_size);
 512 	} else if (g_error_matches (error, RB_ENCODER_ERROR, RB_ENCODER_ERROR_DEST_EXISTS)) {
 513 		rb_debug ("encoder stopped because destination %s already exists",
 514 			  batch->priv->current_dest_uri);
 515 		g_signal_emit (batch, signals[OVERWRITE_PROMPT], 0, batch->priv->current_dest_uri);
 516 		return;
 517 	} else {
 518 		rb_debug ("encoder finished (error: %s)", error->message);
 519 	}
 520 
 521 	track_transfer_completed (batch, dest_size, mediatype, FALSE, error);
 522 }
 523 
 524 static char *
 525 get_extension_from_location (RhythmDBEntry *entry)
 526 {
 527 	char *extension = NULL;
 528 	const char *ext;
 529 	GFile *f;
 530 	char *b;
 531 
 532 	f = g_file_new_for_uri (rhythmdb_entry_get_string (entry, RHYTHMDB_PROP_LOCATION));
 533 	b = g_file_get_basename (f);
 534 	g_object_unref (f);
 535 
 536 	ext = strrchr (b, '.');
 537 	if (ext != NULL) {
 538 		extension = g_strdup (ext+1);
 539 	}
 540 	g_free (b);
 541 
 542 	return extension;
 543 }
 544 
 545 static void
 546 start_encoding (RBTrackTransferBatch *batch, gboolean overwrite)
 547 {
 548 	if (batch->priv->current_encoder != NULL) {
 549 		g_object_unref (batch->priv->current_encoder);
 550 	}
 551 	batch->priv->current_encoder = rb_encoder_new ();
 552 
 553 	g_signal_connect_object (batch->priv->current_encoder, "progress",
 554 				 G_CALLBACK (encoder_progress_cb),
 555 				 batch, 0);
 556 	g_signal_connect_object (batch->priv->current_encoder, "completed",
 557 				 G_CALLBACK (encoder_completed_cb),
 558 				 batch, 0);
 559 
 560 	rb_encoder_encode (batch->priv->current_encoder,
 561 			   batch->priv->current,
 562 			   batch->priv->current_dest_uri,
 563 			   overwrite,
 564 			   batch->priv->current_profile);
 565 }
 566 
 567 static gboolean
 568 start_next (RBTrackTransferBatch *batch)
 569 {
 570 	GstEncodingProfile *profile = NULL;
 571 
 572 	if (batch->priv->cancelled == TRUE) {
 573 		return FALSE;
 574 	}
 575 
 576 	if (batch->priv->entries == NULL) {
 577 		/* guess we must be done.. */
 578 		g_signal_emit (batch, signals[COMPLETE], 0);
 579 		return FALSE;
 580 	}
 581 
 582 	batch->priv->current_fraction = 0.0;
 583 
 584 	rb_debug ("%d entries remain in the batch", g_list_length (batch->priv->entries));
 585 
 586 	while ((batch->priv->entries != NULL) && (batch->priv->cancelled == FALSE)) {
 587 		RhythmDBEntry *entry;
 588 		guint64 filesize;
 589 		gulong duration;
 590 		double fraction;
 591 		GList *n;
 592 		char *media_type;
 593 		char *extension;
 594 
 595 		n = batch->priv->entries;
 596 		batch->priv->entries = g_list_remove_link (batch->priv->entries, n);
 597 		entry = (RhythmDBEntry *)n->data;
 598 		g_list_free_1 (n);
 599 
 600 		rb_debug ("attempting to transfer %s", rhythmdb_entry_get_string (entry, RHYTHMDB_PROP_LOCATION));
 601 
 602 		/* calculate the fraction of the transfer that this entry represents */
 603 		filesize = rhythmdb_entry_get_uint64 (entry, RHYTHMDB_PROP_FILE_SIZE);
 604 		duration = rhythmdb_entry_get_ulong (entry, RHYTHMDB_PROP_DURATION);
 605 		if (batch->priv->total_duration > 0) {
 606 			g_assert (duration > 0);	/* otherwise total_duration would be 0 */
 607 			fraction = ((double)duration) / (double) batch->priv->total_duration;
 608 		} else if (batch->priv->total_size > 0) {
 609 			g_assert (filesize > 0);	/* otherwise total_size would be 0 */
 610 			fraction = ((double)filesize) / (double) batch->priv->total_size;
 611 		} else {
 612 			int count = g_list_length (batch->priv->entries) +
 613 				    g_list_length (batch->priv->done_entries) + 1;
 614 			fraction = 1.0 / ((double)count);
 615 		}
 616 
 617 		profile = NULL;
 618 		if (select_profile_for_entry (batch, entry, &profile, FALSE) == FALSE) {
 619 			rb_debug ("skipping entry %s, can't find an encoding profile",
 620 				  rhythmdb_entry_get_string (entry, RHYTHMDB_PROP_LOCATION));
 621 			rhythmdb_entry_unref (entry);
 622 			batch->priv->total_fraction += fraction;
 623 			continue;
 624 		}
 625 
 626 		if (profile != NULL) {
 627 			media_type = rb_gst_encoding_profile_get_media_type (profile);
 628 			extension = g_strdup (rb_gst_media_type_to_extension (media_type));
 629 
 630 			rb_gst_encoding_profile_set_preset (profile, NULL);
 631 			g_signal_emit (batch, signals[CONFIGURE_PROFILE], 0, media_type, profile);
 632 		} else {
 633 			media_type = rhythmdb_entry_dup_string (entry, RHYTHMDB_PROP_MEDIA_TYPE);
 634 			extension = g_strdup (rb_gst_media_type_to_extension (media_type));
 635 			if (extension == NULL) {
 636 				extension = get_extension_from_location (entry);
 637 			}
 638 		}
 639 
 640 		g_free (batch->priv->current_dest_uri);
 641 		batch->priv->current_dest_uri = NULL;
 642 		g_signal_emit (batch, signals[GET_DEST_URI], 0,
 643 			       entry,
 644 			       media_type,
 645 			       extension,
 646 			       &batch->priv->current_dest_uri);
 647 		g_free (media_type);
 648 		g_free (extension);
 649 
 650 		if (batch->priv->current_dest_uri == NULL) {
 651 			rb_debug ("unable to build destination URI for %s, skipping",
 652 				  rhythmdb_entry_get_string (entry, RHYTHMDB_PROP_LOCATION));
 653 			rhythmdb_entry_unref (entry);
 654 			batch->priv->total_fraction += fraction;
 655 			continue;
 656 		}
 657 
 658 		batch->priv->current = entry;
 659 		batch->priv->current_entry_fraction = fraction;
 660 		batch->priv->current_profile = profile;
 661 		break;
 662 	}
 663 
 664 	if (batch->priv->current != NULL) {
 665 		g_signal_emit (batch, signals[TRACK_STARTED], 0,
 666 			       batch->priv->current,
 667 			       batch->priv->current_dest_uri);
 668 		start_encoding (batch, FALSE);
 669 	}
 670 
 671 	return TRUE;
 672 }
 673 
 674 
 675 
 676 static void
 677 rb_track_transfer_batch_init (RBTrackTransferBatch *batch)
 678 {
 679 	batch->priv = G_TYPE_INSTANCE_GET_PRIVATE (batch,
 680 						   RB_TYPE_TRACK_TRANSFER_BATCH,
 681 						   RBTrackTransferBatchPrivate);
 682 }
 683 
 684 static void
 685 impl_set_property (GObject *object,
 686 		   guint prop_id,
 687 		   const GValue *value,
 688 		   GParamSpec *pspec)
 689 {
 690 	RBTrackTransferBatch *batch = RB_TRACK_TRANSFER_BATCH (object);
 691 	switch (prop_id) {
 692 	case PROP_ENCODING_TARGET:
 693 		batch->priv->target = GST_ENCODING_TARGET (gst_value_dup_mini_object (value));
 694 		break;
 695 	case PROP_SOURCE:
 696 		batch->priv->source = g_value_dup_object (value);
 697 		break;
 698 	case PROP_DESTINATION:
 699 		batch->priv->destination = g_value_dup_object (value);
 700 		break;
 701 	default:
 702 		G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
 703 		break;
 704 	}
 705 }
 706 
 707 static void
 708 impl_get_property (GObject *object,
 709 		   guint prop_id,
 710 		   GValue *value,
 711 		   GParamSpec *pspec)
 712 {
 713 	RBTrackTransferBatch *batch = RB_TRACK_TRANSFER_BATCH (object);
 714 	switch (prop_id) {
 715 	case PROP_ENCODING_TARGET:
 716 		gst_value_set_mini_object (value, GST_MINI_OBJECT (batch->priv->target));
 717 		break;
 718 	case PROP_SOURCE:
 719 		g_value_set_object (value, batch->priv->source);
 720 		break;
 721 	case PROP_DESTINATION:
 722 		g_value_set_object (value, batch->priv->destination);
 723 		break;
 724 	case PROP_TOTAL_ENTRIES:
 725 		{
 726 			int count;
 727 			count = g_list_length (batch->priv->done_entries) +
 728 				g_list_length (batch->priv->entries);
 729 			if (batch->priv->current != NULL) {
 730 				count++;
 731 			}
 732 			g_value_set_int (value, count);
 733 		}
 734 		break;
 735 	case PROP_DONE_ENTRIES:
 736 		g_value_set_int (value, g_list_length (batch->priv->done_entries));
 737 		break;
 738 	case PROP_PROGRESS:
 739 		{
 740 			double p = batch->priv->total_fraction;
 741 			if (batch->priv->current != NULL) {
 742 				p += batch->priv->current_fraction * batch->priv->current_entry_fraction;
 743 			}
 744 			g_value_set_double (value, p);
 745 		}
 746 		break;
 747 	case PROP_ENTRY_LIST:
 748 		{
 749 			GList *l;
 750 			l = g_list_copy (batch->priv->entries);
 751 			if (batch->priv->current != NULL) {
 752 				l = g_list_append (l, batch->priv->current);
 753 			}
 754 			l = g_list_concat (l, g_list_copy (batch->priv->done_entries));
 755 			g_list_foreach (l, (GFunc) rhythmdb_entry_ref, NULL);
 756 			g_value_set_pointer (value, l);
 757 		}
 758 		break;
 759 	default:
 760 		G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
 761 		break;
 762 	}
 763 }
 764 
 765 static void
 766 impl_dispose (GObject *object)
 767 {
 768 	RBTrackTransferBatch *batch = RB_TRACK_TRANSFER_BATCH (object);
 769 
 770 	if (batch->priv->source != NULL) {
 771 		g_object_unref (batch->priv->source);
 772 		batch->priv->source = NULL;
 773 	}
 774 
 775 	if (batch->priv->destination != NULL) {
 776 		g_object_unref (batch->priv->destination);
 777 		batch->priv->destination = NULL;
 778 	}
 779 
 780 	if (batch->priv->target != NULL) {
 781 		gst_encoding_target_unref (batch->priv->target);
 782 		batch->priv->target = NULL;
 783 	}
 784 
 785 	G_OBJECT_CLASS (rb_track_transfer_batch_parent_class)->dispose (object);
 786 }
 787 
 788 static void
 789 impl_finalize (GObject *object)
 790 {
 791 	RBTrackTransferBatch *batch = RB_TRACK_TRANSFER_BATCH (object);
 792 
 793 	rb_list_destroy_free (batch->priv->entries, (GDestroyNotify) rhythmdb_entry_unref);
 794 	rb_list_destroy_free (batch->priv->done_entries, (GDestroyNotify) rhythmdb_entry_unref);
 795 	if (batch->priv->current != NULL) {
 796 		rhythmdb_entry_unref (batch->priv->current);
 797 	}
 798 
 799 	G_OBJECT_CLASS (rb_track_transfer_batch_parent_class)->finalize (object);
 800 }
 801 
 802 static void
 803 rb_track_transfer_batch_class_init (RBTrackTransferBatchClass *klass)
 804 {
 805 	GObjectClass *object_class = G_OBJECT_CLASS (klass);
 806 
 807 	object_class->set_property = impl_set_property;
 808 	object_class->get_property = impl_get_property;
 809 	object_class->finalize = impl_finalize;
 810 	object_class->dispose = impl_dispose;
 811 
 812 	/**
 813 	 * RBTrackTransferBatch:encoding-target:
 814 	 *
 815 	 * A GstEncodingTarget describing allowable target formats.
 816 	 * If NULL, the default set of profiles will be used.
 817 	 */
 818 	g_object_class_install_property (object_class,
 819 					 PROP_ENCODING_TARGET,
 820 					 gst_param_spec_mini_object ("encoding-target",
 821 								     "encoding target",
 822 								     "GstEncodingTarget",
 823 								     GST_TYPE_ENCODING_TARGET,
 824 								     G_PARAM_READWRITE | G_PARAM_CONSTRUCT));
 825 	/**
 826 	 * RBTrackTransferBatch:source:
 827 	 *
 828 	 * The RBSource from which the tracks are being transferred.
 829 	 */
 830 	g_object_class_install_property (object_class,
 831 					 PROP_SOURCE,
 832 					 g_param_spec_object ("source",
 833 							      "source source",
 834 							      "RBSource from which the tracks are being transferred",
 835 							      RB_TYPE_SOURCE,
 836 							      G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
 837 	/**
 838 	 * RBTrackTransferBatch:destination:
 839 	 *
 840 	 * The RBSource to which the tracks are being transferred.
 841 	 */
 842 	g_object_class_install_property (object_class,
 843 					 PROP_DESTINATION,
 844 					 g_param_spec_object ("destination",
 845 							      "destination source",
 846 							      "RBSource to which the tracks are being transferred",
 847 							      RB_TYPE_SOURCE,
 848 							      G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
 849 
 850 	/**
 851 	 * RBTrackTransferBatch:total-entries:
 852 	 *
 853 	 * Total number of entries in the transfer batch.
 854 	 */
 855 	g_object_class_install_property (object_class,
 856 					 PROP_TOTAL_ENTRIES,
 857 					 g_param_spec_int ("total-entries",
 858 							   "total entries",
 859 							   "Number of entries in the batch",
 860 							   0, G_MAXINT, 0,
 861 							   G_PARAM_READABLE));
 862 	/**
 863 	 * RBTrackTransferBatch:done-entries:
 864 	 *
 865 	 * Number of entries in the batch that have been transferred.
 866 	 */
 867 	g_object_class_install_property (object_class,
 868 					 PROP_DONE_ENTRIES,
 869 					 g_param_spec_int ("done-entries",
 870 							   "done entries",
 871 							   "Number of entries already transferred",
 872 							   0, G_MAXINT, 0,
 873 							   G_PARAM_READABLE));
 874 	/**
 875 	 * RBTrackTransferBatch:progress:
 876 	 *
 877 	 * Fraction of the transfer batch that has been processed.
 878 	 */
 879 	g_object_class_install_property (object_class,
 880 					 PROP_PROGRESS,
 881 					 g_param_spec_double ("progress",
 882 							      "progress fraction",
 883 							      "Fraction of the batch that has been transferred",
 884 							      0.0, 1.0, 0.0,
 885 							      G_PARAM_READABLE));
 886 
 887 	/**
 888 	 * RBTrackTransferBatch:entry-list:
 889 	 *
 890 	 * A list of all entries in the batch.
 891 	 */
 892 	g_object_class_install_property (object_class,
 893 					 PROP_ENTRY_LIST,
 894 					 g_param_spec_pointer ("entry-list",
 895 							       "entry list",
 896 							       "list of all entries in the batch",
 897 							       G_PARAM_READABLE));
 898 
 899 	/**
 900 	 * RBTrackTransferBatch::started:
 901 	 * @batch: the #RBTrackTransferBatch
 902 	 *
 903 	 * Emitted when the batch is started.  This will be after
 904 	 * all previous batches have finished, which is not necessarily
 905 	 * when #rb_track_transfer_manager_start_batch is called.
 906 	 */
 907 	signals [STARTED] =
 908 		g_signal_new ("started",
 909 			      G_OBJECT_CLASS_TYPE (object_class),
 910 			      G_SIGNAL_RUN_LAST,
 911 			      G_STRUCT_OFFSET (RBTrackTransferBatchClass, started),
 912 			      NULL, NULL,
 913 			      g_cclosure_marshal_VOID__VOID,
 914 			      G_TYPE_NONE,
 915 			      0);
 916 
 917 	/**
 918 	 * RBTrackTransferBatch::complete:
 919 	 * @batch: the #RBTrackTransferBatch
 920 	 *
 921 	 * Emitted when the batch is complete.  This will be immediately
 922 	 * after the final entry transfer is complete.
 923 	 */
 924 	signals [COMPLETE] =
 925 		g_signal_new ("complete",
 926 			      G_OBJECT_CLASS_TYPE (object_class),
 927 			      G_SIGNAL_RUN_LAST,
 928 			      G_STRUCT_OFFSET (RBTrackTransferBatchClass, complete),
 929 			      NULL, NULL,
 930 			      g_cclosure_marshal_VOID__VOID,
 931 			      G_TYPE_NONE,
 932 			      0);
 933 
 934 	/**
 935 	 * RBTrackTransferBatch::cancelled:
 936 	 * @batch: the #RBTrackTransferBatch
 937 	 *
 938 	 * Emitted when the batch is cancelled.
 939 	 *
 940 	 * hmm.  will 'complete' still be emitted in this case?
 941 	 */
 942 	signals [CANCELLED] =
 943 		g_signal_new ("cancelled",
 944 			      G_OBJECT_CLASS_TYPE (object_class),
 945 			      G_SIGNAL_RUN_LAST,
 946 			      G_STRUCT_OFFSET (RBTrackTransferBatchClass, cancelled),
 947 			      NULL, NULL,
 948 			      g_cclosure_marshal_VOID__VOID,
 949 			      G_TYPE_NONE,
 950 			      0);
 951 
 952 	/**
 953 	 * RBTrackTransferBatch::get-dest-uri:
 954 	 * @batch: the #RBTrackTransferBatch
 955 	 * @entry: the #RhythmDBEntry to be transferred
 956 	 * @mediatype: the destination media type for the transfer
 957 	 * @extension: usual extension for the destionation media type
 958 	 *
 959 	 * The batch emits this to allow the creator to provide a destination
 960 	 * URI for an entry being transferred.  This is emitted after the
 961 	 * output media type is decided, so the usual extension for the media
 962 	 * type can be taken into consideration.
 963 	 */
 964 	signals [GET_DEST_URI] =
 965 		g_signal_new ("get-dest-uri",
 966 			      G_OBJECT_CLASS_TYPE (object_class),
 967 			      G_SIGNAL_RUN_LAST,
 968 			      G_STRUCT_OFFSET (RBTrackTransferBatchClass, get_dest_uri),
 969 			      NULL, NULL,
 970 			      rb_marshal_STRING__BOXED_STRING_STRING,
 971 			      G_TYPE_STRING,
 972 			      3, RHYTHMDB_TYPE_ENTRY, G_TYPE_STRING, G_TYPE_STRING);
 973 
 974 	/**
 975 	 * RBTrackTransferBatch::overwrite-prompt:
 976 	 * @batch: the #RBTrackTransferBatch
 977 	 * @uri: the destination URI that already exists
 978 	 *
 979 	 * Emitted when the destination URI for a transfer already exists.
 980 	 * The handler must call _rb_track_transfer_batch_continue or
 981 	 * _rb_track_transfer_batch_cancel when it has figured out what to
 982 	 * do.
 983 	 */
 984 	signals [OVERWRITE_PROMPT] =
 985 		g_signal_new ("overwrite-prompt",
 986 			      G_OBJECT_CLASS_TYPE (object_class),
 987 			      G_SIGNAL_RUN_LAST,
 988 			      G_STRUCT_OFFSET (RBTrackTransferBatchClass, overwrite_prompt),
 989 			      NULL, NULL,
 990 			      g_cclosure_marshal_VOID__STRING,
 991 			      G_TYPE_NONE,
 992 			      1, G_TYPE_STRING);
 993 
 994 	/**
 995 	 * RBTrackTransferBatch::track-started:
 996 	 * @batch: the #RBTrackTransferBatch
 997 	 * @entry: the #RhythmDBEntry being transferred
 998 	 * @dest: the destination URI for the transfer
 999 	 *
1000 	 * Emitted when a new entry is about to be transferred.
1001 	 * This will be emitted for each entry in the batch, unless
1002 	 * the batch is cancelled.
1003 	 */
1004 	signals [TRACK_STARTED] =
1005 		g_signal_new ("track-started",
1006 			      G_OBJECT_CLASS_TYPE (object_class),
1007 			      G_SIGNAL_RUN_LAST,
1008 			      G_STRUCT_OFFSET (RBTrackTransferBatchClass, track_started),
1009 			      NULL, NULL,
1010 			      rb_marshal_VOID__BOXED_STRING,
1011 			      G_TYPE_NONE,
1012 			      2, RHYTHMDB_TYPE_ENTRY, G_TYPE_STRING);
1013 
1014 	/**
1015 	 * RBTrackTransferBatch::track-progress:
1016 	 * @batch: the #RBTrackTransferBatch
1017 	 * @entry: the #RhythmDBEntry being transferred
1018 	 * @dest: the destination URI for the transfer
1019 	 * @done: some measure of how much of the transfer is done
1020 	 * @total: the total amount of that same measure
1021 	 * @fraction: the fraction of the transfer that is done
1022 	 *
1023 	 * Emitted regularly throughout the transfer to allow progress bars
1024 	 * and other UI elements to be updated.
1025 	 */
1026 	signals [TRACK_PROGRESS] =
1027 		g_signal_new ("track-progress",
1028 			      G_OBJECT_CLASS_TYPE (object_class),
1029 			      G_SIGNAL_RUN_LAST,
1030 			      G_STRUCT_OFFSET (RBTrackTransferBatchClass, track_progress),
1031 			      NULL, NULL,
1032 			      rb_marshal_VOID__BOXED_STRING_INT_INT_DOUBLE,
1033 			      G_TYPE_NONE,
1034 			      5, RHYTHMDB_TYPE_ENTRY, G_TYPE_STRING, G_TYPE_INT, G_TYPE_INT, G_TYPE_DOUBLE);
1035 
1036 	/**
1037 	 * RBTrackTransferBatch::track-done:
1038 	 * @batch: the #RBTrackTransferBatch
1039 	 * @entry: the #RhythmDBEntry that was transferred
1040 	 * @dest: the destination URI for the transfer
1041 	 * @dest_size: size of the destination file
1042 	 * @dest_mediatype: the media type of the destination file
1043 	 * @error: any error that occurred during transfer
1044 	 *
1045 	 * Emitted when a track transfer is complete, whether because
1046 	 * the track was fully transferred, because an error occurred,
1047 	 * or because the batch was cancelled (maybe..).
1048 	 */
1049 	signals [TRACK_DONE] =
1050 		g_signal_new ("track-done",
1051 			      G_OBJECT_CLASS_TYPE (object_class),
1052 			      G_SIGNAL_RUN_LAST,
1053 			      G_STRUCT_OFFSET (RBTrackTransferBatchClass, track_done),
1054 			      NULL, NULL,
1055 			      rb_marshal_VOID__BOXED_STRING_UINT64_STRING_POINTER,
1056 			      G_TYPE_NONE,
1057 			      5, RHYTHMDB_TYPE_ENTRY, G_TYPE_STRING, G_TYPE_UINT64, G_TYPE_STRING, G_TYPE_POINTER);
1058 
1059 	/**
1060 	 * RBTrackTransferBatch::configure-profile:
1061 	 * @batch: the #RBTrackTransferBatch
1062 	 * @mediatype: the target media type
1063 	 * @profile: the #GstEncodingProfile
1064 	 *
1065 	 * Emitted to allow configuration of encoding profile settings
1066 	 * (mostly by setting presets on sub-profiles).
1067 	 */
1068 	signals [CONFIGURE_PROFILE] =
1069 		g_signal_new ("configure-profile",
1070 			      G_OBJECT_CLASS_TYPE (object_class),
1071 			      G_SIGNAL_RUN_LAST,
1072 			      G_STRUCT_OFFSET (RBTrackTransferBatchClass, configure_profile),
1073 			      NULL, NULL,
1074 			      rb_marshal_VOID__STRING_POINTER,
1075 			      G_TYPE_NONE,
1076 			      2, G_TYPE_STRING, GST_TYPE_ENCODING_PROFILE);
1077 
1078 	g_type_class_add_private (klass, sizeof (RBTrackTransferBatchPrivate));
1079 }