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 }