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-debug.h"
40 #include "rb-util.h"
41 #include "rb-gst-media-types.h"
42 #include "rb-task-progress.h"
43 #include "rb-file-helpers.h"
44 
45 enum
46 {
47 	STARTED,
48 	COMPLETE,
49 	CANCELLED,
50 	GET_DEST_URI,
51 	OVERWRITE_PROMPT,
52 	TRACK_STARTED,
53 	TRACK_PROGRESS,
54 	TRACK_DONE,
55 	LAST_SIGNAL
56 };
57 
58 enum
59 {
60 	PROP_0,
61 	PROP_ENCODING_TARGET,
62 	PROP_SETTINGS,
63 	PROP_QUEUE,
64 	PROP_SOURCE,
65 	PROP_DESTINATION,
66 	PROP_TOTAL_ENTRIES,
67 	PROP_DONE_ENTRIES,
68 	PROP_PROGRESS,
69 	PROP_ENTRY_LIST,
70 	PROP_TASK_LABEL,
71 	PROP_TASK_DETAIL,
72 	PROP_TASK_PROGRESS,
73 	PROP_TASK_OUTCOME,
74 	PROP_TASK_NOTIFY,
75 	PROP_TASK_CANCELLABLE
76 };
77 
78 static void	rb_track_transfer_batch_class_init (RBTrackTransferBatchClass *klass);
79 static void	rb_track_transfer_batch_init (RBTrackTransferBatch *batch);
80 static void	rb_track_transfer_batch_task_progress_init (RBTaskProgressInterface *iface);
81 
82 static gboolean start_next (RBTrackTransferBatch *batch);
83 static void start_encoding (RBTrackTransferBatch *batch, gboolean overwrite);
84 static void track_transfer_completed (RBTrackTransferBatch *batch,
85 				      const char *dest_uri,
86 				      guint64 dest_size,
87 				      const char *mediatype,
88 				      gboolean skipped,
89 				      GError *error);
90 
91 static guint	signals[LAST_SIGNAL] = { 0 };
92 
93 struct _RBTrackTransferBatchPrivate
94 {
95 	RBTrackTransferQueue *queue;
96 
97 	GstEncodingTarget *target;
98 	GSettings *settings;
99 	GList *missing_plugin_profiles;
100 
101 	RBSource *source;
102 	RBSource *destination;
103 
104 	GList *entries;
105 	GList *done_entries;
106 
107 	guint64 total_duration;
108 	guint64 total_size;
109 	double total_fraction;
110 
111 	RhythmDBEntry *current;
112 	double current_entry_fraction;
113 	char *current_dest_uri;
114 	gboolean current_dest_uri_sanitized;
115 	double current_fraction;
116 	RBEncoder *current_encoder;
117 	GstEncodingProfile *current_profile;
118 	gboolean cancelled;
119 
120 	char *task_label;
121 	gboolean task_notify;
122 };
123 
124 G_DEFINE_TYPE_EXTENDED (RBTrackTransferBatch,
125 			rb_track_transfer_batch,
126 			G_TYPE_OBJECT,
127 			0,
128 			G_IMPLEMENT_INTERFACE (RB_TYPE_TASK_PROGRESS, rb_track_transfer_batch_task_progress_init));
129 
130 /**
131  * SECTION:rb-track-transfer-batch
132  * @short_description: batch track transfer job
133  *
134  * Manages the transfer of a set of tracks (using #RBEncoder), providing overall
135  * status information and allowing the transfer to be cancelled as a single unit.
136  */
137 
138 /**
139  * rb_track_transfer_batch_new:
140  * @target: a #GstEncodingTarget describing allowable encodings (or NULL for defaults)
141  * @source: the #RBSource from which the entries are to be transferred
142  * @destination: the #RBSource to which the entries are to be transferred
143  * @queue: the #RBTrackTransferQueue instance
144  *
145  * Creates a new transfer batch with the specified encoding target.  If no target
146  * is specified, the default target will be used with the user's preferred
147  * encoding type.
148  *
149  * One or more entries must be added to the batch (using #rb_track_transfer_batch_add)
150  * before the batch can be started (#rb_track_transfer_manager_start_batch).
151  *
152  * Return value: new #RBTrackTransferBatch object
153  */
154 RBTrackTransferBatch *
rb_track_transfer_batch_new(GstEncodingTarget * target,GSettings * settings,GObject * source,GObject * destination,GObject * queue)155 rb_track_transfer_batch_new (GstEncodingTarget *target,
156 			     GSettings *settings,
157 			     GObject *source,
158 			     GObject *destination,
159 			     GObject *queue)
160 {
161 	GObject *obj;
162 
163 	obj = g_object_new (RB_TYPE_TRACK_TRANSFER_BATCH,
164 			    "encoding-target", target,
165 			    "settings", settings,
166 			    "source", source,
167 			    "destination", destination,
168 			    "queue", queue,
169 			    NULL);
170 	return RB_TRACK_TRANSFER_BATCH (obj);
171 }
172 
173 /**
174  * rb_track_transfer_batch_add:
175  * @batch: a #RBTrackTransferBatch
176  * @entry: the source #RhythmDBEntry to transfer
177  *
178  * Adds an entry to be transferred.
179  */
180 void
rb_track_transfer_batch_add(RBTrackTransferBatch * batch,RhythmDBEntry * entry)181 rb_track_transfer_batch_add (RBTrackTransferBatch *batch, RhythmDBEntry *entry)
182 {
183 	batch->priv->entries = g_list_append (batch->priv->entries, rhythmdb_entry_ref (entry));
184 }
185 
186 static gboolean
select_profile_for_entry(RBTrackTransferBatch * batch,RhythmDBEntry * entry,GstEncodingProfile ** rprofile,gboolean allow_missing)187 select_profile_for_entry (RBTrackTransferBatch *batch, RhythmDBEntry *entry, GstEncodingProfile **rprofile, gboolean allow_missing)
188 {
189 	const char *source_media_type = rhythmdb_entry_get_string (entry, RHYTHMDB_PROP_MEDIA_TYPE);
190 	const GList *p;
191 	int best = 0;
192 
193 	for (p = gst_encoding_target_get_profiles (batch->priv->target); p != NULL; p = p->next) {
194 		GstEncodingProfile *profile = GST_ENCODING_PROFILE (p->data);
195 		char *profile_media_type;
196 		const char *preferred_media_type;
197 		gboolean transcode_lossless;
198 		gboolean is_preferred;
199 		gboolean is_lossless;
200 		gboolean is_source;
201 		gboolean is_missing;
202 		int rank;
203 
204 		profile_media_type = rb_gst_encoding_profile_get_media_type (profile);
205 		if (batch->priv->settings) {
206 			preferred_media_type = g_settings_get_string (batch->priv->settings, "media-type");
207 			if (rb_gst_media_type_is_lossless (preferred_media_type)) {
208 				transcode_lossless = FALSE;
209 			} else {
210 				transcode_lossless = g_settings_get_boolean (batch->priv->settings, "transcode-lossless");
211 			}
212 
213 			is_preferred = (rb_gst_media_type_matches_profile (profile, preferred_media_type));
214 		} else {
215 			preferred_media_type = NULL;
216 			transcode_lossless = FALSE;
217 			is_preferred = FALSE;
218 		}
219 
220 		is_missing = (g_list_find (batch->priv->missing_plugin_profiles, profile) != NULL);
221 		if (g_str_has_prefix (source_media_type, "audio/x-raw") == FALSE) {
222 			is_source = rb_gst_media_type_matches_profile (profile, source_media_type);
223 		} else {
224 			/* always transcode raw audio */
225 			is_source = FALSE;
226 		}
227 
228 		if (profile_media_type != NULL) {
229 			is_lossless = (rb_gst_media_type_is_lossless (profile_media_type));
230 		} else {
231 			is_lossless = (rb_gst_media_type_is_lossless (source_media_type));
232 		}
233 
234 		if (is_missing && allow_missing == FALSE && is_source == FALSE) {
235 			/* this only applies if transcoding would be required */
236 			rb_debug ("can't use encoding %s due to missing plugins", profile_media_type);
237 			rank = 0;
238 		} else if (transcode_lossless && is_lossless) {
239 			/* this overrides is_source so all lossless files get transcoded */
240 			rb_debug ("don't want lossless encoding %s", profile_media_type);
241 			rank = 0;
242 		} else if (is_source) {
243 			/* this overrides is_preferred so we don't transcode unneccessarily */
244 			rb_debug ("can use source encoding %s", profile_media_type);
245 			rank = 100;
246 			profile = NULL;
247 		} else if (is_preferred) {
248 			/* otherwise, always use the preferred encoding if available */
249 			rb_debug ("can use preferred encoding %s", profile_media_type);
250 			rank = 50;
251 		} else if (is_lossless == FALSE) {
252 			/* if we can't use the preferred encoding, we prefer lossy encodings over lossless, for space reasons */
253 			rb_debug ("can use lossy encoding %s", profile_media_type);
254 			rank = 25;
255 		} else {
256 			rb_debug ("can use lossless encoding %s", profile_media_type);
257 			rank = 10;
258 		}
259 
260 		g_free (profile_media_type);
261 		if (rank > best) {
262 			*rprofile = profile;
263 			best = rank;
264 		}
265 	}
266 
267 	return (best > 0);
268 }
269 
270 /**
271  * rb_track_transfer_batch_check_profiles:
272  * @batch: a #RBTrackTransferBatch
273  * @missing_plugin_profiles: (out) (element-type GstPbutils.EncodingProfile): holds a #GList of #GstEncodingProfiles on return
274  * @error_count: holds the number of entries that cannot be transferred on return
275  *
276  * Checks that all entries in the batch can be transferred in a format
277  * supported by the destination.  If no encoding profile is available for
278  * some entries, but installing additional plugins could make a profile
279  * available, a list of profiles that require additional plugins is returned.
280  *
281  * Return value: %TRUE if some entries can be transferred without additional plugins
282  */
283 gboolean
rb_track_transfer_batch_check_profiles(RBTrackTransferBatch * batch,GList ** missing_plugin_profiles,int * error_count)284 rb_track_transfer_batch_check_profiles (RBTrackTransferBatch *batch, GList **missing_plugin_profiles, int *error_count)
285 {
286 	RBEncoder *encoder = rb_encoder_new ();
287 	gboolean ret = FALSE;
288 	const GList *l;
289 
290 	rb_debug ("checking profiles");
291 
292 	/* first, figure out which profiles that we care about would require additional plugins to use */
293 	g_list_free (batch->priv->missing_plugin_profiles);
294 	batch->priv->missing_plugin_profiles = NULL;
295 
296 	for (l = gst_encoding_target_get_profiles (batch->priv->target); l != NULL; l = l->next) {
297 		GstEncodingProfile *profile = GST_ENCODING_PROFILE (l->data);
298 		char *profile_media_type;
299 		profile_media_type = rb_gst_encoding_profile_get_media_type (profile);
300 		if (profile_media_type != NULL &&
301 		    (rb_gst_media_type_is_lossless (profile_media_type) == FALSE) &&
302 		    rb_encoder_get_missing_plugins (encoder, profile, NULL, NULL)) {
303 			batch->priv->missing_plugin_profiles = g_list_append (batch->priv->missing_plugin_profiles, profile);
304 		}
305 		g_free (profile_media_type);
306 	}
307 	g_object_unref (encoder);
308 
309 	rb_debug ("have %d profiles with missing plugins", g_list_length (batch->priv->missing_plugin_profiles));
310 
311 	for (l = batch->priv->entries; l != NULL; l = l->next) {
312 		RhythmDBEntry *entry = (RhythmDBEntry *)l->data;
313 		GstEncodingProfile *profile;
314 
315 		profile = NULL;
316 		if (select_profile_for_entry (batch, entry, &profile, FALSE) == TRUE) {
317 			if (profile != NULL) {
318 				rb_debug ("found profile %s for %s",
319 					  gst_encoding_profile_get_name (profile),
320 					  rhythmdb_entry_get_string (entry, RHYTHMDB_PROP_LOCATION));
321 			} else {
322 				rb_debug ("copying entry %s", rhythmdb_entry_get_string (entry, RHYTHMDB_PROP_LOCATION));
323 			}
324 			ret = TRUE;
325 			continue;
326 		}
327 
328 		(*error_count)++;
329 		if (select_profile_for_entry (batch, entry, &profile, TRUE) == FALSE) {
330 			rb_debug ("unable to transfer %s (media type %s)",
331 				  rhythmdb_entry_get_string (entry, RHYTHMDB_PROP_LOCATION),
332 				  rhythmdb_entry_get_string (entry, RHYTHMDB_PROP_MEDIA_TYPE));
333 		} else {
334 			rb_debug ("require additional plugins to transfer %s (media type %s)",
335 				  rhythmdb_entry_get_string (entry, RHYTHMDB_PROP_LOCATION),
336 				  rhythmdb_entry_get_string (entry, RHYTHMDB_PROP_MEDIA_TYPE));
337 			if (*missing_plugin_profiles == NULL) {
338 				*missing_plugin_profiles = g_list_copy (batch->priv->missing_plugin_profiles);
339 			}
340 		}
341 	}
342 	return ret;
343 }
344 
345 /**
346  * rb_track_transfer_batch_cancel:
347  * @batch: a #RBTrackTransferBatch
348  *
349  * Cancels the batch.
350  */
351 void
rb_track_transfer_batch_cancel(RBTrackTransferBatch * batch)352 rb_track_transfer_batch_cancel (RBTrackTransferBatch *batch)
353 {
354 	rb_track_transfer_queue_cancel_batch (batch->priv->queue, batch);
355 }
356 
357 static void
task_progress_cancel(RBTaskProgress * progress)358 task_progress_cancel (RBTaskProgress *progress)
359 {
360 	rb_track_transfer_batch_cancel (RB_TRACK_TRANSFER_BATCH (progress));
361 }
362 
363 /**
364  * _rb_track_transfer_batch_start:
365  * @batch: a #RBTrackTransferBatch
366  *
367  * Starts the batch transfer.  Only to be called by the #RBTrackTransferQueue.
368  */
369 void
_rb_track_transfer_batch_start(RBTrackTransferBatch * batch)370 _rb_track_transfer_batch_start (RBTrackTransferBatch *batch)
371 {
372 	gboolean total_duration_valid;
373 	gboolean total_size_valid;
374 	gboolean origin_valid;
375 	guint64 filesize;
376 	gulong duration;
377 	RBSource *origin = NULL;
378 	RBShell *shell;
379 	GList *l;
380 
381 	g_object_get (batch->priv->queue, "shell", &shell, NULL);
382 
383 	/* calculate total duration and file size and figure out the
384 	 * origin source if we weren't given one to start with.
385 	 */
386 	total_duration_valid = TRUE;
387 	total_size_valid = TRUE;
388 	origin_valid = TRUE;
389 	for (l = batch->priv->entries; l != NULL; l = l->next) {
390 		RhythmDBEntry *entry = (RhythmDBEntry *)l->data;
391 
392 		filesize = rhythmdb_entry_get_uint64 (entry, RHYTHMDB_PROP_FILE_SIZE);
393 		if (total_size_valid && filesize > 0) {
394 			batch->priv->total_size += filesize;
395 		} else {
396 			total_size_valid = FALSE;
397 			batch->priv->total_size = 0;
398 		}
399 
400 		duration = rhythmdb_entry_get_ulong (entry, RHYTHMDB_PROP_DURATION);
401 		if (total_duration_valid && duration > 0) {
402 			batch->priv->total_duration += duration;
403 		} else {
404 			total_duration_valid = FALSE;
405 			batch->priv->total_duration = 0;
406 		}
407 
408 		if (batch->priv->source == NULL) {
409 			RhythmDBEntryType *entry_type;
410 			RBSource *entry_origin;
411 
412 			entry_type = rhythmdb_entry_get_entry_type (entry);
413 			entry_origin = rb_shell_get_source_by_entry_type (shell, entry_type);
414 			if (origin == NULL && origin_valid  == TRUE) {
415 				origin = entry_origin;
416 			} else if (origin != entry_origin) {
417 				origin = NULL;
418 				origin_valid = FALSE;
419 			}
420 		}
421 	}
422 
423 	g_object_unref (shell);
424 
425 	if (origin != NULL) {
426 		batch->priv->source = origin;
427 	}
428 
429 	batch->priv->cancelled = FALSE;
430 	batch->priv->total_fraction = 0.0;
431 
432 	g_signal_emit (batch, signals[STARTED], 0);
433 	g_object_notify (G_OBJECT (batch), "task-progress");
434 	g_object_notify (G_OBJECT (batch), "task-detail");
435 
436 	start_next (batch);
437 }
438 
439 /**
440  * _rb_track_transfer_batch_cancel:
441  * @batch: a #RBTrackTransferBatch
442  *
443  * Cancels a batch transfer.  Only to be called by the #RBTrackTransferQueue.
444  */
445 void
_rb_track_transfer_batch_cancel(RBTrackTransferBatch * batch)446 _rb_track_transfer_batch_cancel (RBTrackTransferBatch *batch)
447 {
448 	batch->priv->cancelled = TRUE;
449 	rb_debug ("batch being cancelled");
450 
451 	if (batch->priv->current_encoder != NULL) {
452 		rb_encoder_cancel (batch->priv->current_encoder);
453 
454 		/* other things take care of cleaning up the encoder */
455 	}
456 
457 	g_signal_emit (batch, signals[CANCELLED], 0);
458 	g_object_notify (G_OBJECT (batch), "task-outcome");
459 
460 	/* anything else? */
461 }
462 
463 /**
464  * _rb_track_transfer_batch_continue:
465  * @batch: a #RBTrackTransferBatch
466  * @overwrite: if %TRUE, overwrite the current file, otherwise skip
467  *
468  * Continues a transfer that was suspended because the current
469  * destination URI exists.  Only to be called by the #RBTrackTransferQueue.
470  */
471 void
_rb_track_transfer_batch_continue(RBTrackTransferBatch * batch,gboolean overwrite)472 _rb_track_transfer_batch_continue (RBTrackTransferBatch *batch, gboolean overwrite)
473 {
474 	if (overwrite) {
475 		start_encoding (batch, TRUE);
476 	} else {
477 		track_transfer_completed (batch, NULL, 0, NULL, TRUE, NULL);
478 	}
479 }
480 
481 static void
emit_progress(RBTrackTransferBatch * batch)482 emit_progress (RBTrackTransferBatch *batch)
483 {
484 	int done;
485 	int total;
486 	double fraction;
487 
488 	g_object_get (batch,
489 		      "total-entries", &total,
490 		      "done-entries", &done,
491 		      "progress", &fraction,
492 		      NULL);
493 	g_signal_emit (batch, signals[TRACK_PROGRESS], 0,
494 		       batch->priv->current,
495 		       batch->priv->current_dest_uri,
496 		       done,
497 		       total,
498 		       fraction);
499 	g_object_notify (G_OBJECT (batch), "task-progress");
500 }
501 
502 static void
encoder_progress_cb(RBEncoder * encoder,double fraction,RBTrackTransferBatch * batch)503 encoder_progress_cb (RBEncoder *encoder, double fraction, RBTrackTransferBatch *batch)
504 {
505 	batch->priv->current_fraction = fraction;
506 	emit_progress (batch);
507 }
508 
509 static void
track_transfer_completed(RBTrackTransferBatch * batch,const char * dest_uri,guint64 dest_size,const char * mediatype,gboolean skipped,GError * error)510 track_transfer_completed (RBTrackTransferBatch *batch,
511 			  const char *dest_uri,
512 			  guint64 dest_size,
513 			  const char *mediatype,
514 			  gboolean skipped,
515 			  GError *error)
516 {
517 	RhythmDBEntry *entry;
518 
519 	entry = batch->priv->current;
520 	batch->priv->current = NULL;
521 
522 	batch->priv->current_profile = NULL;
523 
524 	/* update batch state to reflect that the track is done */
525 	batch->priv->total_fraction += batch->priv->current_entry_fraction;
526 	batch->priv->done_entries = g_list_append (batch->priv->done_entries, entry);
527 
528 	if (batch->priv->cancelled == FALSE) {
529 		/* keep ourselves alive until the end of the function, since it's
530 		 * possible that a signal handler will cancel us.
531 		 */
532 		g_object_ref (batch);
533 		if (skipped == FALSE) {
534 			g_signal_emit (batch, signals[TRACK_DONE], 0,
535 				       entry,
536 				       dest_uri,
537 				       dest_size,
538 				       mediatype,
539 				       error);
540 		}
541 
542 		start_next (batch);
543 
544 		g_object_unref (batch);
545 	}
546 }
547 
548 static void
encoder_completed_cb(RBEncoder * encoder,const char * dest_uri,guint64 dest_size,const char * mediatype,GError * error,RBTrackTransferBatch * batch)549 encoder_completed_cb (RBEncoder *encoder,
550 		      const char *dest_uri,
551 		      guint64 dest_size,
552 		      const char *mediatype,
553 		      GError *error,
554 		      RBTrackTransferBatch *batch)
555 {
556 	g_object_unref (batch->priv->current_encoder);
557 	batch->priv->current_encoder = NULL;
558 
559 	if (error == NULL) {
560 		rb_debug ("encoder finished (size %" G_GUINT64_FORMAT ")", dest_size);
561 	} else if (g_error_matches (error, RB_ENCODER_ERROR, RB_ENCODER_ERROR_DEST_EXISTS)) {
562 		rb_debug ("encoder stopped because destination %s already exists", dest_uri);
563 		g_signal_emit (batch, signals[OVERWRITE_PROMPT], 0, dest_uri);
564 		return;
565 	} else {
566 		rb_debug ("encoder finished (error: %s)", error->message);
567 	}
568 
569 	track_transfer_completed (batch, dest_uri, dest_size, mediatype, FALSE, error);
570 }
571 
572 static char *
get_extension_from_location(RhythmDBEntry * entry)573 get_extension_from_location (RhythmDBEntry *entry)
574 {
575 	char *extension = NULL;
576 	const char *ext;
577 	GFile *f;
578 	char *b;
579 
580 	f = g_file_new_for_uri (rhythmdb_entry_get_string (entry, RHYTHMDB_PROP_LOCATION));
581 	b = g_file_get_basename (f);
582 	g_object_unref (f);
583 
584 	ext = strrchr (b, '.');
585 	if (ext != NULL) {
586 		extension = g_strdup (ext+1);
587 	}
588 	g_free (b);
589 
590 	return extension;
591 }
592 
593 static void
start_encoding(RBTrackTransferBatch * batch,gboolean overwrite)594 start_encoding (RBTrackTransferBatch *batch, gboolean overwrite)
595 {
596 	if (batch->priv->current_encoder != NULL) {
597 		g_object_unref (batch->priv->current_encoder);
598 	}
599 	batch->priv->current_encoder = rb_encoder_new ();
600 
601 	g_signal_connect_object (batch->priv->current_encoder, "progress",
602 				 G_CALLBACK (encoder_progress_cb),
603 				 batch, 0);
604 	g_signal_connect_object (batch->priv->current_encoder, "completed",
605 				 G_CALLBACK (encoder_completed_cb),
606 				 batch, 0);
607 
608 	rb_encoder_encode (batch->priv->current_encoder,
609 			   batch->priv->current,
610 			   batch->priv->current_dest_uri,
611 			   overwrite,
612 			   batch->priv->current_profile);
613 }
614 
615 static void
create_parent_dirs_task(GTask * task,gpointer source_object,gpointer task_data,GCancellable * cancellable)616 create_parent_dirs_task (GTask *task, gpointer source_object, gpointer task_data, GCancellable *cancellable)
617 {
618 	RBTrackTransferBatch *batch;
619 	GError *error = NULL;
620 
621 	batch = RB_TRACK_TRANSFER_BATCH (source_object);
622 	rb_debug ("creating parent dirs for %s", batch->priv->current_dest_uri);
623 	if (rb_uri_create_parent_dirs (batch->priv->current_dest_uri, &error) == FALSE) {
624 		g_task_return_error (task, error);
625 	} else {
626 		g_task_return_boolean (task, TRUE);
627 	}
628 	g_object_unref (task);
629 }
630 
631 static void
create_parent_dirs_cb(GObject * source_object,GAsyncResult * result,gpointer data)632 create_parent_dirs_cb (GObject *source_object, GAsyncResult *result, gpointer data)
633 {
634 	RBTrackTransferBatch *batch;
635 	GError *error = NULL;
636 
637 	batch = RB_TRACK_TRANSFER_BATCH (source_object);
638 	if (g_task_propagate_boolean (G_TASK (result), &error) == FALSE) {
639 
640 		if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_INVALID_FILENAME) &&
641 		    (batch->priv->current_dest_uri_sanitized == FALSE)) {
642 			GTask *task;
643 			char *dest;
644 
645 			g_clear_error (&error);
646 			dest = rb_sanitize_uri_for_filesystem (batch->priv->current_dest_uri, "msdos");
647 			g_free (batch->priv->current_dest_uri);
648 			batch->priv->current_dest_uri = dest;
649 			batch->priv->current_dest_uri_sanitized = TRUE;
650 
651 			rb_debug ("retrying parent dir creation with sanitized uri: %s", dest);
652 			task = g_task_new (batch, NULL, create_parent_dirs_cb, NULL);
653 			g_task_run_in_thread (task, create_parent_dirs_task);
654 		} else {
655 			rb_debug ("failed to create parent directories for %s", batch->priv->current_dest_uri);
656 			track_transfer_completed (batch, NULL, 0, NULL, FALSE, error);
657 		}
658 	} else {
659 		rb_debug ("parent directories for %s created", batch->priv->current_dest_uri);
660 		g_signal_emit (batch, signals[TRACK_STARTED], 0,
661 			       batch->priv->current,
662 			       batch->priv->current_dest_uri);
663 		start_encoding (batch, FALSE);
664 		g_object_notify (G_OBJECT (batch), "task-detail");
665 	}
666 }
667 
668 static gboolean
start_next(RBTrackTransferBatch * batch)669 start_next (RBTrackTransferBatch *batch)
670 {
671 	GstEncodingProfile *profile = NULL;
672 
673 	if (batch->priv->cancelled == TRUE) {
674 		return FALSE;
675 	}
676 
677 	rb_debug ("%d entries remain in the batch", g_list_length (batch->priv->entries));
678 	batch->priv->current_fraction = 0.0;
679 
680 	while ((batch->priv->entries != NULL) && (batch->priv->cancelled == FALSE)) {
681 		RhythmDBEntry *entry;
682 		guint64 filesize;
683 		gulong duration;
684 		double fraction;
685 		GList *n;
686 		char *media_type;
687 		char *extension;
688 
689 		n = batch->priv->entries;
690 		batch->priv->entries = g_list_remove_link (batch->priv->entries, n);
691 		entry = (RhythmDBEntry *)n->data;
692 		g_list_free_1 (n);
693 
694 		rb_debug ("attempting to transfer %s", rhythmdb_entry_get_string (entry, RHYTHMDB_PROP_LOCATION));
695 
696 		/* calculate the fraction of the transfer that this entry represents */
697 		filesize = rhythmdb_entry_get_uint64 (entry, RHYTHMDB_PROP_FILE_SIZE);
698 		duration = rhythmdb_entry_get_ulong (entry, RHYTHMDB_PROP_DURATION);
699 		if (batch->priv->total_duration > 0) {
700 			g_assert (duration > 0);	/* otherwise total_duration would be 0 */
701 			fraction = ((double)duration) / (double) batch->priv->total_duration;
702 		} else if (batch->priv->total_size > 0) {
703 			g_assert (filesize > 0);	/* otherwise total_size would be 0 */
704 			fraction = ((double)filesize) / (double) batch->priv->total_size;
705 		} else {
706 			int count = g_list_length (batch->priv->entries) +
707 				    g_list_length (batch->priv->done_entries) + 1;
708 			fraction = 1.0 / ((double)count);
709 		}
710 
711 		profile = NULL;
712 		if (select_profile_for_entry (batch, entry, &profile, FALSE) == FALSE) {
713 			rb_debug ("skipping entry %s, can't find an encoding profile",
714 				  rhythmdb_entry_get_string (entry, RHYTHMDB_PROP_LOCATION));
715 			rhythmdb_entry_unref (entry);
716 			batch->priv->total_fraction += fraction;
717 			continue;
718 		}
719 
720 		if (profile != NULL) {
721 			media_type = rb_gst_encoding_profile_get_media_type (profile);
722 			extension = g_strdup (rb_gst_media_type_to_extension (media_type));
723 
724 			rb_gst_encoding_profile_set_preset (profile, NULL);
725 			if (batch->priv->settings != NULL) {
726 				GVariant *preset_settings;
727 				char *active_preset;
728 
729 				preset_settings = g_settings_get_value (batch->priv->settings,
730 									"media-type-presets");
731 				active_preset = NULL;
732 				g_variant_lookup (preset_settings, media_type, "s", &active_preset);
733 
734 				rb_debug ("setting preset %s for media type %s",
735 					  active_preset, media_type);
736 				rb_gst_encoding_profile_set_preset (profile, active_preset);
737 
738 				g_free (active_preset);
739 			}
740 		} else {
741 			media_type = rhythmdb_entry_dup_string (entry, RHYTHMDB_PROP_MEDIA_TYPE);
742 			extension = g_strdup (rb_gst_media_type_to_extension (media_type));
743 			if (extension == NULL) {
744 				extension = get_extension_from_location (entry);
745 			}
746 		}
747 
748 		g_free (batch->priv->current_dest_uri);
749 		batch->priv->current_dest_uri = NULL;
750 		batch->priv->current_dest_uri_sanitized = FALSE;
751 		g_signal_emit (batch, signals[GET_DEST_URI], 0,
752 			       entry,
753 			       media_type,
754 			       extension,
755 			       &batch->priv->current_dest_uri);
756 		g_free (media_type);
757 		g_free (extension);
758 
759 		if (batch->priv->current_dest_uri == NULL) {
760 			rb_debug ("unable to build destination URI for %s, skipping",
761 				  rhythmdb_entry_get_string (entry, RHYTHMDB_PROP_LOCATION));
762 			rhythmdb_entry_unref (entry);
763 			batch->priv->total_fraction += fraction;
764 			continue;
765 		}
766 
767 		batch->priv->current = entry;
768 		batch->priv->current_entry_fraction = fraction;
769 		batch->priv->current_profile = profile;
770 		break;
771 	}
772 
773 	if (batch->priv->current != NULL) {
774 		GTask *task;
775 
776 		task = g_task_new (batch, NULL, create_parent_dirs_cb, NULL);
777 		g_task_run_in_thread (task, create_parent_dirs_task);
778 	} else {
779 		g_signal_emit (batch, signals[COMPLETE], 0);
780 		g_object_notify (G_OBJECT (batch), "task-outcome");
781 		return FALSE;
782 	}
783 
784 	return TRUE;
785 }
786 
787 
788 
789 static void
rb_track_transfer_batch_init(RBTrackTransferBatch * batch)790 rb_track_transfer_batch_init (RBTrackTransferBatch *batch)
791 {
792 	batch->priv = G_TYPE_INSTANCE_GET_PRIVATE (batch,
793 						   RB_TYPE_TRACK_TRANSFER_BATCH,
794 						   RBTrackTransferBatchPrivate);
795 }
796 
797 static void
impl_set_property(GObject * object,guint prop_id,const GValue * value,GParamSpec * pspec)798 impl_set_property (GObject *object,
799 		   guint prop_id,
800 		   const GValue *value,
801 		   GParamSpec *pspec)
802 {
803 	RBTrackTransferBatch *batch = RB_TRACK_TRANSFER_BATCH (object);
804 	switch (prop_id) {
805 	case PROP_ENCODING_TARGET:
806 		batch->priv->target = GST_ENCODING_TARGET (g_value_dup_object (value));
807 		break;
808 	case PROP_SETTINGS:
809 		batch->priv->settings = g_value_dup_object (value);
810 		break;
811 	case PROP_QUEUE:
812 		batch->priv->queue = g_value_get_object (value);
813 		break;
814 	case PROP_SOURCE:
815 		batch->priv->source = g_value_dup_object (value);
816 		break;
817 	case PROP_DESTINATION:
818 		batch->priv->destination = g_value_dup_object (value);
819 		break;
820 	case PROP_TASK_LABEL:
821 		batch->priv->task_label = g_value_dup_string (value);
822 		break;
823 	case PROP_TASK_DETAIL:
824 		/* ignore */
825 		break;
826 	case PROP_TASK_PROGRESS:
827 		/* ignore */
828 		break;
829 	case PROP_TASK_OUTCOME:
830 		/* ignore */
831 		break;
832 	case PROP_TASK_NOTIFY:
833 		batch->priv->task_notify = g_value_get_boolean (value);
834 		break;
835 	case PROP_TASK_CANCELLABLE:
836 		/* ignore */
837 		break;
838 	default:
839 		G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
840 		break;
841 	}
842 }
843 
844 static void
impl_get_property(GObject * object,guint prop_id,GValue * value,GParamSpec * pspec)845 impl_get_property (GObject *object,
846 		   guint prop_id,
847 		   GValue *value,
848 		   GParamSpec *pspec)
849 {
850 	RBTrackTransferBatch *batch = RB_TRACK_TRANSFER_BATCH (object);
851 	switch (prop_id) {
852 	case PROP_ENCODING_TARGET:
853 		g_value_set_object (value, batch->priv->target);
854 		break;
855 	case PROP_SETTINGS:
856 		g_value_set_object (value, batch->priv->settings);
857 		break;
858 	case PROP_QUEUE:
859 		g_value_set_object (value, batch->priv->queue);
860 		break;
861 	case PROP_SOURCE:
862 		g_value_set_object (value, batch->priv->source);
863 		break;
864 	case PROP_DESTINATION:
865 		g_value_set_object (value, batch->priv->destination);
866 		break;
867 	case PROP_TOTAL_ENTRIES:
868 		{
869 			int count;
870 			count = g_list_length (batch->priv->done_entries) +
871 				g_list_length (batch->priv->entries);
872 			if (batch->priv->current != NULL) {
873 				count++;
874 			}
875 			g_value_set_int (value, count);
876 		}
877 		break;
878 	case PROP_DONE_ENTRIES:
879 		g_value_set_int (value, g_list_length (batch->priv->done_entries));
880 		break;
881 	case PROP_TASK_PROGRESS:
882 	case PROP_PROGRESS:		/* needed? */
883 		{
884 			double p = batch->priv->total_fraction;
885 			if (batch->priv->current != NULL) {
886 				p += batch->priv->current_fraction * batch->priv->current_entry_fraction;
887 			}
888 			g_value_set_double (value, p);
889 		}
890 		break;
891 	case PROP_ENTRY_LIST:
892 		{
893 			GList *l;
894 			l = g_list_copy (batch->priv->entries);
895 			if (batch->priv->current != NULL) {
896 				l = g_list_append (l, batch->priv->current);
897 			}
898 			l = g_list_concat (l, g_list_copy (batch->priv->done_entries));
899 			g_list_foreach (l, (GFunc) rhythmdb_entry_ref, NULL);
900 			g_value_set_pointer (value, l);
901 		}
902 		break;
903 	case PROP_TASK_LABEL:
904 		g_value_set_string (value, batch->priv->task_label);
905 		break;
906 	case PROP_TASK_DETAIL:
907 		{
908 			int done;
909 			int total;
910 
911 			done = g_list_length (batch->priv->done_entries);
912 			total = done + g_list_length (batch->priv->entries);
913 			if (batch->priv->current) {
914 				total++;
915 				done++;
916 			}
917 			g_value_take_string (value, g_strdup_printf (_("%d of %d"), done, total));
918 		}
919 		break;
920 	case PROP_TASK_OUTCOME:
921 		if (batch->priv->cancelled) {
922 			g_value_set_enum (value, RB_TASK_OUTCOME_CANCELLED);
923 		} else if ((batch->priv->entries == NULL) && (batch->priv->done_entries != NULL)) {
924 			g_value_set_enum (value, RB_TASK_OUTCOME_COMPLETE);
925 		} else {
926 			g_value_set_enum (value, RB_TASK_OUTCOME_NONE);
927 		}
928 		break;
929 	case PROP_TASK_NOTIFY:
930 		/* we might want to notify sometimes, but we never did before */
931 		g_value_set_boolean (value, FALSE);
932 		break;
933 	case PROP_TASK_CANCELLABLE:
934 		g_value_set_boolean (value, TRUE);
935 		break;
936 	default:
937 		G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
938 		break;
939 	}
940 }
941 
942 static void
impl_dispose(GObject * object)943 impl_dispose (GObject *object)
944 {
945 	RBTrackTransferBatch *batch = RB_TRACK_TRANSFER_BATCH (object);
946 
947 	g_clear_object (&batch->priv->source);
948 	g_clear_object (&batch->priv->destination);
949 	g_clear_object (&batch->priv->settings);
950 
951 	if (batch->priv->target != NULL) {
952 		gst_encoding_target_unref (batch->priv->target);
953 		batch->priv->target = NULL;
954 	}
955 
956 	G_OBJECT_CLASS (rb_track_transfer_batch_parent_class)->dispose (object);
957 }
958 
959 static void
impl_finalize(GObject * object)960 impl_finalize (GObject *object)
961 {
962 	RBTrackTransferBatch *batch = RB_TRACK_TRANSFER_BATCH (object);
963 
964 	rb_list_destroy_free (batch->priv->entries, (GDestroyNotify) rhythmdb_entry_unref);
965 	rb_list_destroy_free (batch->priv->done_entries, (GDestroyNotify) rhythmdb_entry_unref);
966 	if (batch->priv->current != NULL) {
967 		rhythmdb_entry_unref (batch->priv->current);
968 	}
969 	g_free (batch->priv->task_label);
970 
971 	G_OBJECT_CLASS (rb_track_transfer_batch_parent_class)->finalize (object);
972 }
973 
974 static void
rb_track_transfer_batch_task_progress_init(RBTaskProgressInterface * interface)975 rb_track_transfer_batch_task_progress_init (RBTaskProgressInterface *interface)
976 {
977 	interface->cancel = task_progress_cancel;
978 }
979 
980 static void
rb_track_transfer_batch_class_init(RBTrackTransferBatchClass * klass)981 rb_track_transfer_batch_class_init (RBTrackTransferBatchClass *klass)
982 {
983 	GObjectClass *object_class = G_OBJECT_CLASS (klass);
984 
985 	object_class->set_property = impl_set_property;
986 	object_class->get_property = impl_get_property;
987 	object_class->finalize = impl_finalize;
988 	object_class->dispose = impl_dispose;
989 
990 	/**
991 	 * RBTrackTransferBatch:encoding-target:
992 	 *
993 	 * A GstEncodingTarget describing allowable target formats.
994 	 * If NULL, the default set of profiles will be used.
995 	 */
996 	g_object_class_install_property (object_class,
997 					 PROP_ENCODING_TARGET,
998 					 g_param_spec_object ("encoding-target",
999 							      "encoding target",
1000 							      "GstEncodingTarget",
1001 							      GST_TYPE_ENCODING_TARGET,
1002 							      G_PARAM_READWRITE | G_PARAM_CONSTRUCT));
1003 	/**
1004 	 * RBTrackTransferBatch:settings
1005 	 *
1006 	 * GSettings instance holding profile preferences
1007 	 */
1008 	g_object_class_install_property (object_class,
1009 					 PROP_SETTINGS,
1010 					 g_param_spec_object ("settings",
1011 							      "profile settings",
1012 							      "GSettings instance holding profile settings",
1013 							      G_TYPE_SETTINGS,
1014 							      G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
1015 	/**
1016 	 * RBTrackTransferBatch:queue
1017 	 *
1018 	 * The #RBTrackTransferQueue instance
1019 	 */
1020 	g_object_class_install_property (object_class,
1021 					 PROP_QUEUE,
1022 					 g_param_spec_object ("queue",
1023 							      "transfer queue",
1024 							      "RBTrackTransferQueue instance",
1025 							      RB_TYPE_TRACK_TRANSFER_QUEUE,
1026 							      G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
1027 	/**
1028 	 * RBTrackTransferBatch:source:
1029 	 *
1030 	 * The RBSource from which the tracks are being transferred.
1031 	 */
1032 	g_object_class_install_property (object_class,
1033 					 PROP_SOURCE,
1034 					 g_param_spec_object ("source",
1035 							      "source source",
1036 							      "RBSource from which the tracks are being transferred",
1037 							      RB_TYPE_SOURCE,
1038 							      G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
1039 	/**
1040 	 * RBTrackTransferBatch:destination:
1041 	 *
1042 	 * The RBSource to which the tracks are being transferred.
1043 	 */
1044 	g_object_class_install_property (object_class,
1045 					 PROP_DESTINATION,
1046 					 g_param_spec_object ("destination",
1047 							      "destination source",
1048 							      "RBSource to which the tracks are being transferred",
1049 							      RB_TYPE_SOURCE,
1050 							      G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
1051 
1052 	/**
1053 	 * RBTrackTransferBatch:total-entries:
1054 	 *
1055 	 * Total number of entries in the transfer batch.
1056 	 */
1057 	g_object_class_install_property (object_class,
1058 					 PROP_TOTAL_ENTRIES,
1059 					 g_param_spec_int ("total-entries",
1060 							   "total entries",
1061 							   "Number of entries in the batch",
1062 							   0, G_MAXINT, 0,
1063 							   G_PARAM_READABLE));
1064 	/**
1065 	 * RBTrackTransferBatch:done-entries:
1066 	 *
1067 	 * Number of entries in the batch that have been transferred.
1068 	 */
1069 	g_object_class_install_property (object_class,
1070 					 PROP_DONE_ENTRIES,
1071 					 g_param_spec_int ("done-entries",
1072 							   "done entries",
1073 							   "Number of entries already transferred",
1074 							   0, G_MAXINT, 0,
1075 							   G_PARAM_READABLE));
1076 	/**
1077 	 * RBTrackTransferBatch:progress:
1078 	 *
1079 	 * Fraction of the transfer batch that has been processed.
1080 	 */
1081 	g_object_class_install_property (object_class,
1082 					 PROP_PROGRESS,
1083 					 g_param_spec_double ("progress",
1084 							      "progress fraction",
1085 							      "Fraction of the batch that has been transferred",
1086 							      0.0, 1.0, 0.0,
1087 							      G_PARAM_READABLE));
1088 
1089 	/**
1090 	 * RBTrackTransferBatch:entry-list:
1091 	 *
1092 	 * A list of all entries in the batch.
1093 	 */
1094 	g_object_class_install_property (object_class,
1095 					 PROP_ENTRY_LIST,
1096 					 g_param_spec_pointer ("entry-list",
1097 							       "entry list",
1098 							       "list of all entries in the batch",
1099 							       G_PARAM_READABLE));
1100 
1101 	g_object_class_override_property (object_class, PROP_TASK_LABEL, "task-label");
1102 	g_object_class_override_property (object_class, PROP_TASK_DETAIL, "task-detail");
1103 	g_object_class_override_property (object_class, PROP_TASK_PROGRESS, "task-progress");
1104 	g_object_class_override_property (object_class, PROP_TASK_OUTCOME, "task-outcome");
1105 	g_object_class_override_property (object_class, PROP_TASK_NOTIFY, "task-notify");
1106 	g_object_class_override_property (object_class, PROP_TASK_CANCELLABLE, "task-cancellable");
1107 
1108 	/**
1109 	 * RBTrackTransferBatch::started:
1110 	 * @batch: the #RBTrackTransferBatch
1111 	 *
1112 	 * Emitted when the batch is started.  This will be after
1113 	 * all previous batches have finished, which is not necessarily
1114 	 * when #rb_track_transfer_manager_start_batch is called.
1115 	 */
1116 	signals [STARTED] =
1117 		g_signal_new ("started",
1118 			      G_OBJECT_CLASS_TYPE (object_class),
1119 			      G_SIGNAL_RUN_LAST,
1120 			      G_STRUCT_OFFSET (RBTrackTransferBatchClass, started),
1121 			      NULL, NULL,
1122 			      NULL,
1123 			      G_TYPE_NONE,
1124 			      0);
1125 
1126 	/**
1127 	 * RBTrackTransferBatch::complete:
1128 	 * @batch: the #RBTrackTransferBatch
1129 	 *
1130 	 * Emitted when the batch is complete.  This will be immediately
1131 	 * after the final entry transfer is complete.
1132 	 */
1133 	signals [COMPLETE] =
1134 		g_signal_new ("complete",
1135 			      G_OBJECT_CLASS_TYPE (object_class),
1136 			      G_SIGNAL_RUN_LAST,
1137 			      G_STRUCT_OFFSET (RBTrackTransferBatchClass, complete),
1138 			      NULL, NULL,
1139 			      NULL,
1140 			      G_TYPE_NONE,
1141 			      0);
1142 
1143 	/**
1144 	 * RBTrackTransferBatch::cancelled:
1145 	 * @batch: the #RBTrackTransferBatch
1146 	 *
1147 	 * Emitted when the batch is cancelled.
1148 	 *
1149 	 * hmm.  will 'complete' still be emitted in this case?
1150 	 */
1151 	signals [CANCELLED] =
1152 		g_signal_new ("cancelled",
1153 			      G_OBJECT_CLASS_TYPE (object_class),
1154 			      G_SIGNAL_RUN_LAST,
1155 			      G_STRUCT_OFFSET (RBTrackTransferBatchClass, cancelled),
1156 			      NULL, NULL,
1157 			      NULL,
1158 			      G_TYPE_NONE,
1159 			      0);
1160 
1161 	/**
1162 	 * RBTrackTransferBatch::get-dest-uri:
1163 	 * @batch: the #RBTrackTransferBatch
1164 	 * @entry: the #RhythmDBEntry to be transferred
1165 	 * @mediatype: the destination media type for the transfer
1166 	 * @extension: usual extension for the destionation media type
1167 	 *
1168 	 * The batch emits this to allow the creator to provide a destination
1169 	 * URI for an entry being transferred.  This is emitted after the
1170 	 * output media type is decided, so the usual extension for the media
1171 	 * type can be taken into consideration.
1172 	 */
1173 	signals [GET_DEST_URI] =
1174 		g_signal_new ("get-dest-uri",
1175 			      G_OBJECT_CLASS_TYPE (object_class),
1176 			      G_SIGNAL_RUN_LAST,
1177 			      G_STRUCT_OFFSET (RBTrackTransferBatchClass, get_dest_uri),
1178 			      NULL, NULL,
1179 			      NULL,
1180 			      G_TYPE_STRING,
1181 			      3, RHYTHMDB_TYPE_ENTRY, G_TYPE_STRING, G_TYPE_STRING);
1182 
1183 	/**
1184 	 * RBTrackTransferBatch::overwrite-prompt:
1185 	 * @batch: the #RBTrackTransferBatch
1186 	 * @uri: the destination URI that already exists
1187 	 *
1188 	 * Emitted when the destination URI for a transfer already exists.
1189 	 * The handler must call _rb_track_transfer_batch_continue or
1190 	 * _rb_track_transfer_batch_cancel when it has figured out what to
1191 	 * do.
1192 	 */
1193 	signals [OVERWRITE_PROMPT] =
1194 		g_signal_new ("overwrite-prompt",
1195 			      G_OBJECT_CLASS_TYPE (object_class),
1196 			      G_SIGNAL_RUN_LAST,
1197 			      G_STRUCT_OFFSET (RBTrackTransferBatchClass, overwrite_prompt),
1198 			      NULL, NULL,
1199 			      NULL,
1200 			      G_TYPE_NONE,
1201 			      1, G_TYPE_STRING);
1202 
1203 	/**
1204 	 * RBTrackTransferBatch::track-started:
1205 	 * @batch: the #RBTrackTransferBatch
1206 	 * @entry: the #RhythmDBEntry being transferred
1207 	 * @dest: the destination URI for the transfer
1208 	 *
1209 	 * Emitted when a new entry is about to be transferred.
1210 	 * This will be emitted for each entry in the batch, unless
1211 	 * the batch is cancelled.
1212 	 */
1213 	signals [TRACK_STARTED] =
1214 		g_signal_new ("track-started",
1215 			      G_OBJECT_CLASS_TYPE (object_class),
1216 			      G_SIGNAL_RUN_LAST,
1217 			      G_STRUCT_OFFSET (RBTrackTransferBatchClass, track_started),
1218 			      NULL, NULL,
1219 			      NULL,
1220 			      G_TYPE_NONE,
1221 			      2, RHYTHMDB_TYPE_ENTRY, G_TYPE_STRING);
1222 
1223 	/**
1224 	 * RBTrackTransferBatch::track-progress:
1225 	 * @batch: the #RBTrackTransferBatch
1226 	 * @entry: the #RhythmDBEntry being transferred
1227 	 * @dest: the destination URI for the transfer
1228 	 * @done: some measure of how much of the transfer is done
1229 	 * @total: the total amount of that same measure
1230 	 * @fraction: the fraction of the transfer that is done
1231 	 *
1232 	 * Emitted regularly throughout the transfer to allow progress bars
1233 	 * and other UI elements to be updated.
1234 	 */
1235 	signals [TRACK_PROGRESS] =
1236 		g_signal_new ("track-progress",
1237 			      G_OBJECT_CLASS_TYPE (object_class),
1238 			      G_SIGNAL_RUN_LAST,
1239 			      G_STRUCT_OFFSET (RBTrackTransferBatchClass, track_progress),
1240 			      NULL, NULL,
1241 			      NULL,
1242 			      G_TYPE_NONE,
1243 			      5, RHYTHMDB_TYPE_ENTRY, G_TYPE_STRING, G_TYPE_INT, G_TYPE_INT, G_TYPE_DOUBLE);
1244 
1245 	/**
1246 	 * RBTrackTransferBatch::track-done:
1247 	 * @batch: the #RBTrackTransferBatch
1248 	 * @entry: the #RhythmDBEntry that was transferred
1249 	 * @dest: the destination URI for the transfer
1250 	 * @dest_size: size of the destination file
1251 	 * @dest_mediatype: the media type of the destination file
1252 	 * @error: any error that occurred during transfer
1253 	 *
1254 	 * Emitted when a track transfer is complete, whether because
1255 	 * the track was fully transferred, because an error occurred,
1256 	 * or because the batch was cancelled (maybe..).
1257 	 */
1258 	signals [TRACK_DONE] =
1259 		g_signal_new ("track-done",
1260 			      G_OBJECT_CLASS_TYPE (object_class),
1261 			      G_SIGNAL_RUN_LAST,
1262 			      G_STRUCT_OFFSET (RBTrackTransferBatchClass, track_done),
1263 			      NULL, NULL, NULL,
1264 			      G_TYPE_NONE,
1265 			      5, RHYTHMDB_TYPE_ENTRY, G_TYPE_STRING, G_TYPE_UINT64, G_TYPE_STRING, G_TYPE_POINTER);
1266 
1267 	g_type_class_add_private (klass, sizeof (RBTrackTransferBatchPrivate));
1268 }
1269