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