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 "rb-track-transfer-queue.h"
32 #include "rb-encoder.h"
33 #include "rb-library-source.h"
34 #include "rb-debug.h"
35 #include "rb-dialog.h"
36 #include "rb-alert-dialog.h"
37 #include "rb-gst-media-types.h"
38 #include "rb-missing-plugins.h"
39 
40 #include <glib/gi18n.h>
41 
42 #include <gst/gst.h>
43 #include <gst/pbutils/install-plugins.h>
44 
45 enum
46 {
47 	PROP_0,
48 	PROP_SHELL,
49 	PROP_BATCH
50 };
51 
52 enum
53 {
54 	TRANSFER_PROGRESS,
55 	MISSING_PLUGINS,
56 	LAST_SIGNAL
57 };
58 
59 static void rb_track_transfer_queue_class_init	(RBTrackTransferQueueClass *klass);
60 static void rb_track_transfer_queue_init	(RBTrackTransferQueue *queue);
61 
62 static void start_next_batch (RBTrackTransferQueue *queue);
63 
64 static guint signals[LAST_SIGNAL] = { 0 };
65 
66 struct _RBTrackTransferQueuePrivate
67 {
68 	RBShell *shell;
69 
70 	GQueue *batch_queue;
71 	enum {
72 		OVERWRITE_PROMPT,
73 		OVERWRITE_ALL,
74 		OVERWRITE_NONE
75 	} overwrite_decision;
76 	RBTrackTransferBatch *current;
77 	time_t current_start_time;
78 };
79 
G_DEFINE_TYPE(RBTrackTransferQueue,rb_track_transfer_queue,G_TYPE_OBJECT)80 G_DEFINE_TYPE (RBTrackTransferQueue, rb_track_transfer_queue, G_TYPE_OBJECT)
81 
82 /**
83  * SECTION:rb-track-transfer-queue
84  * @short_description: track transfer queue and surrounding junk
85  *
86  */
87 
88 /**
89  * rb_track_transfer_queue_new:
90  * @shell: the #RBShell
91  *
92  * Creates the #RBTrackTransferQueue instance
93  *
94  * Return value: the #RBTrackTransferQueue
95  */
96 RBTrackTransferQueue *
97 rb_track_transfer_queue_new (RBShell *shell)
98 {
99 	return g_object_new (RB_TYPE_TRACK_TRANSFER_QUEUE, "shell", shell, NULL);
100 }
101 
102 static void
overwrite_response_cb(GtkDialog * dialog,int response,RBTrackTransferQueue * queue)103 overwrite_response_cb (GtkDialog *dialog, int response, RBTrackTransferQueue *queue)
104 {
105 	gtk_widget_destroy (GTK_WIDGET (dialog));
106 
107 	switch (response) {
108 	case GTK_RESPONSE_YES:
109 		rb_debug ("replacing existing file");
110 		_rb_track_transfer_batch_continue (queue->priv->current, TRUE);
111 		break;
112 
113 	case GTK_RESPONSE_NO:
114 		rb_debug ("skipping existing file");
115 		_rb_track_transfer_batch_continue (queue->priv->current, FALSE);
116 		break;
117 
118 	case GTK_RESPONSE_REJECT:
119 		rb_debug ("skipping all existing files");
120 		queue->priv->overwrite_decision = OVERWRITE_NONE;
121 		_rb_track_transfer_batch_continue (queue->priv->current, FALSE);
122 		break;
123 
124 	case GTK_RESPONSE_ACCEPT:
125 		rb_debug ("replacing all existing files");
126 		queue->priv->overwrite_decision = OVERWRITE_ALL;
127 		_rb_track_transfer_batch_continue (queue->priv->current, TRUE);
128 		break;
129 
130 	case GTK_RESPONSE_CANCEL:
131 	case GTK_RESPONSE_DELETE_EVENT:		/* not sure what the user really wants here */
132 		rb_debug ("cancelling batch");
133 		rb_track_transfer_queue_cancel_batch (queue, queue->priv->current);
134 		break;
135 
136 	default:
137 		g_assert_not_reached ();
138 		break;
139 	}
140 }
141 
142 static void
overwrite_prompt(RBTrackTransferBatch * batch,const char * uri,RBTrackTransferQueue * queue)143 overwrite_prompt (RBTrackTransferBatch *batch, const char *uri, RBTrackTransferQueue *queue)
144 {
145 	switch (queue->priv->overwrite_decision) {
146 	case OVERWRITE_PROMPT:
147 	{
148 		GtkWindow *window;
149 		GtkWidget *dialog;
150 		GFile *file;
151 		GFileInfo *info;
152 		char *text;
153 		char *free_name;
154 		const char *display_name;
155 
156 		free_name = NULL;
157 		display_name = NULL;
158 		file = g_file_new_for_uri (uri);
159 		info = g_file_query_info (file,
160 					  G_FILE_ATTRIBUTE_STANDARD_DISPLAY_NAME,
161 					  G_FILE_QUERY_INFO_NONE,
162 					  NULL,
163 					  NULL);
164 		if (info != NULL) {
165 			display_name = g_file_info_get_display_name (info);
166 		}
167 
168 		if (display_name == NULL) {
169 			free_name = g_file_get_uri (file);
170 			display_name = free_name;
171 		}
172 
173 		g_object_get (queue->priv->shell, "window", &window, NULL);
174 		text = g_strdup_printf (_("The file \"%s\" already exists. Do you want to replace it?"),
175 					display_name);
176 		dialog = rb_alert_dialog_new (window,
177 					      0,
178 					      GTK_MESSAGE_WARNING,
179 					      GTK_BUTTONS_NONE,
180 					      text,
181 					      NULL);
182 		g_object_unref (window);
183 		g_free (text);
184 
185 		rb_alert_dialog_set_details_label (RB_ALERT_DIALOG (dialog), NULL);
186 		gtk_dialog_add_buttons (GTK_DIALOG (dialog),
187 					_("_Cancel"), GTK_RESPONSE_CANCEL,
188 					_("_Skip"), GTK_RESPONSE_NO,
189 					_("_Replace"), GTK_RESPONSE_YES,
190 					_("S_kip All"), GTK_RESPONSE_REJECT,
191 					_("Replace _All"), GTK_RESPONSE_ACCEPT,
192 					NULL);
193 
194 		g_signal_connect (dialog, "response", G_CALLBACK (overwrite_response_cb), queue);
195 		gtk_widget_show (GTK_WIDGET (dialog));
196 		g_free (free_name);
197 		if (info != NULL) {
198 			g_object_unref (info);
199 		}
200 		g_object_unref (file);
201 		break;
202 	}
203 
204 	case OVERWRITE_ALL:
205 		rb_debug ("already decided to replace all existing files");
206 		_rb_track_transfer_batch_continue (batch, TRUE);
207 		break;
208 
209 	case OVERWRITE_NONE:
210 		rb_debug ("already decided to skip all existing files");
211 		_rb_track_transfer_batch_continue (batch, FALSE);
212 		break;
213 
214 	default:
215 		g_assert_not_reached ();
216 	}
217 }
218 
219 static void
batch_complete(RBTrackTransferBatch * batch,RBTrackTransferQueue * queue)220 batch_complete (RBTrackTransferBatch *batch, RBTrackTransferQueue *queue)
221 {
222 	if (batch != queue->priv->current) {
223 		rb_debug ("what?");
224 		return;
225 	}
226 
227 	/* batch itself will ensure we get a progress signal showing the
228 	 * whole batch complete, so we don't need one here.
229 	 */
230 
231 	queue->priv->current = NULL;
232 	g_object_unref (batch);
233 
234 	start_next_batch (queue);
235 }
236 
237 static int
estimate_time_left(RBTrackTransferQueue * queue,double progress)238 estimate_time_left (RBTrackTransferQueue *queue, double progress)
239 {
240 	time_t now;
241 	time_t elapsed;
242 	double total_time;
243 
244 	time (&now);
245 	elapsed = now - queue->priv->current_start_time;
246 	total_time = ((double)elapsed) / progress;
247 	return ((time_t) total_time) - elapsed;
248 }
249 
250 static void
batch_progress(RBTrackTransferBatch * batch,RhythmDBEntry * entry,const char * dest,int done,int total,double fraction,RBTrackTransferQueue * queue)251 batch_progress (RBTrackTransferBatch *batch,
252 		RhythmDBEntry *entry,
253 		const char *dest,
254 		int done,
255 		int total,
256 		double fraction,
257 		RBTrackTransferQueue *queue)
258 {
259 	g_signal_emit (queue, signals[TRANSFER_PROGRESS], 0, done, total, fraction, estimate_time_left (queue, fraction));
260 }
261 
262 static void
actually_start_batch(RBTrackTransferQueue * queue)263 actually_start_batch (RBTrackTransferQueue *queue)
264 {
265 	g_signal_connect_object (queue->priv->current,
266 				 "overwrite-prompt",
267 				 G_CALLBACK (overwrite_prompt),
268 				 queue, 0);
269 	g_signal_connect_object (queue->priv->current,
270 				 "complete",
271 				 G_CALLBACK (batch_complete),
272 				 queue, 0);
273 	g_signal_connect_object (queue->priv->current,
274 				 "track-progress",
275 				 G_CALLBACK (batch_progress),
276 				 queue, 0);
277 	_rb_track_transfer_batch_start (queue->priv->current);
278 }
279 
280 static GPtrArray *
get_missing_plugin_strings(GList * profiles,gboolean get_descriptions)281 get_missing_plugin_strings (GList *profiles, gboolean get_descriptions)
282 {
283 	RBEncoder *encoder;
284 	GPtrArray *strings;
285 	GList *l;
286 
287 	encoder = rb_encoder_new ();
288 	strings = g_ptr_array_new_with_free_func (g_free);
289 	for (l = profiles; l != NULL; l = l->next) {
290 		GstEncodingProfile *profile = GST_ENCODING_PROFILE (l->data);
291 		char **details, **descriptions;
292 		char **d;
293 		int i;
294 
295 		rb_encoder_get_missing_plugins (encoder, profile, &details, &descriptions);
296 		d = get_descriptions ? descriptions : details;
297 		for (i = 0; d[i] != NULL; i++) {
298 			g_ptr_array_add (strings, g_strdup (d[i]));
299 		}
300 		g_strfreev (details);
301 		g_strfreev (descriptions);
302 	}
303 	g_ptr_array_add (strings, NULL);
304 	g_object_unref (encoder);
305 
306 	return strings;
307 }
308 
309 static void
missing_plugins_retry_cb(gpointer inst,gboolean retry,RBTrackTransferQueue * queue)310 missing_plugins_retry_cb (gpointer inst, gboolean retry, RBTrackTransferQueue *queue)
311 {
312 	rb_debug ("plugin install finished (retry %d), checking media types again", retry);
313 	g_queue_push_head (queue->priv->batch_queue, queue->priv->current);
314 	queue->priv->current = NULL;
315 	start_next_batch (queue);
316 }
317 
318 static void
missing_encoder_response_cb(GtkDialog * dialog,gint response,RBTrackTransferQueue * queue)319 missing_encoder_response_cb (GtkDialog *dialog, gint response, RBTrackTransferQueue *queue)
320 {
321 	GClosure *retry;
322 	GstEncodingTarget *target;
323 	GPtrArray *details;
324 	GList *profiles;
325 	const GList *l;
326 	RBEncoder *encoder;
327 
328 	switch (response) {
329 	case GTK_RESPONSE_YES:
330 		/* 'continue' -> start the batch */
331 		rb_debug ("starting batch regardless of missing plugins");
332 		actually_start_batch (queue);
333 		break;
334 
335 	case GTK_RESPONSE_CANCEL:
336 	case GTK_RESPONSE_DELETE_EVENT:
337 		/* 'cancel' -> cancel the batch and start the next one */
338 		rb_debug ("cancelling batch");
339 		_rb_track_transfer_batch_cancel (queue->priv->current);
340 		g_object_unref (queue->priv->current);
341 		queue->priv->current = NULL;
342 
343 		start_next_batch (queue);
344 		break;
345 
346 	case GTK_RESPONSE_ACCEPT:
347 		/* 'install plugins' -> try to install encoder/muxer */
348 
349 		/* get profiles that need plugins installed */
350 		profiles = NULL;
351 		encoder = rb_encoder_new ();
352 		g_object_get (queue->priv->current, "encoding-target", &target, NULL);
353 		for (l = gst_encoding_target_get_profiles (target); l != NULL; l = l->next) {
354 			GstEncodingProfile *profile = GST_ENCODING_PROFILE (l->data);
355 			char *profile_media_type;
356 			profile_media_type = rb_gst_encoding_profile_get_media_type (profile);
357 			if (profile_media_type != NULL &&
358 			    (rb_gst_media_type_is_lossless (profile_media_type) == FALSE) &&
359 			    rb_encoder_get_missing_plugins (encoder, profile, NULL, NULL)) {
360 				profiles = g_list_append (profiles, profile);
361 			}
362 			g_free (profile_media_type);
363 		}
364 		g_object_unref (encoder);
365 		g_object_unref (target);
366 
367 		if (profiles == NULL) {
368 			rb_debug ("apparently we don't need any plugins any more");
369 			actually_start_batch (queue);
370 			break;
371 		}
372 
373 		rb_debug ("attempting plugin installation");
374 		details = get_missing_plugin_strings (profiles, FALSE);
375 		retry = g_cclosure_new ((GCallback) missing_plugins_retry_cb,
376 					g_object_ref (queue),
377 					(GClosureNotify) g_object_unref);
378 		g_closure_set_marshal (retry, g_cclosure_marshal_VOID__BOOLEAN);
379 		if (rb_missing_plugins_install ((const char **)details->pdata, FALSE, retry)) {
380 			rb_debug ("attempting to install missing plugins for transcoding");
381 		} else {
382 			rb_debug ("proceeding without the missing plugins for transcoding");
383 			actually_start_batch (queue);
384 		}
385 
386 		g_closure_sink (retry);
387 		g_ptr_array_free (details, TRUE);
388 		g_list_free (profiles);
389 		break;
390 
391 	default:
392 		g_assert_not_reached ();
393 	}
394 
395 	gtk_widget_destroy (GTK_WIDGET (dialog));
396 }
397 
398 static void
start_next_batch(RBTrackTransferQueue * queue)399 start_next_batch (RBTrackTransferQueue *queue)
400 {
401 	int count;
402 	int total;
403 	gboolean can_continue;
404 	GtkWidget *dialog;
405 	GtkWindow *window;
406 	GList *profiles = NULL;
407 	char *message;
408 
409 	if (queue->priv->current != NULL) {
410 		return;
411 	}
412 
413 	queue->priv->current = RB_TRACK_TRANSFER_BATCH (g_queue_pop_head (queue->priv->batch_queue));
414 	g_object_notify (G_OBJECT (queue), "batch");
415 
416 	if (queue->priv->current == NULL) {
417 		/* indicate to anyone watching that we're not doing anything */
418 		g_signal_emit (queue, signals[TRANSFER_PROGRESS], 0, 0, 0, 0.0, 0);
419 		return;
420 	}
421 
422 	queue->priv->overwrite_decision = OVERWRITE_PROMPT;
423 	g_object_get (queue->priv->current, "total-entries", &total, NULL);
424 
425 	count = 0;
426 	can_continue = rb_track_transfer_batch_check_profiles (queue->priv->current,
427 							       &profiles,
428 							       &count);
429 
430 	if (can_continue && count == 0 && profiles == NULL) {
431 		/* no problems, go ahead */
432 		actually_start_batch (queue);
433 		return;
434 	}
435 
436 	if (profiles == NULL) {
437 		const char *str;
438 		str = ngettext ("%d file cannot be transferred as it must be converted into "
439 				"a format supported by the target device but no suitable "
440 				"encoding profiles are available",
441 				"%d files cannot be transferred as they must be converted into "
442 				"a format supported by the target device but no suitable "
443 				"encoding profiles are available",
444 				count);
445 		message = g_strdup_printf (str, count);
446 	} else {
447 		GPtrArray *descriptions;
448 		GstEncodingTarget *target;
449 		char *plugins;
450 		gboolean is_library;
451 
452 		descriptions = get_missing_plugin_strings (profiles, TRUE);
453 		plugins = g_strjoinv ("\n", (char **)descriptions->pdata);
454 
455 		/* this is a tiny bit hackish */
456 		g_object_get (queue->priv->current, "encoding-target", &target, NULL);
457 		is_library = (g_strcmp0 (gst_encoding_target_get_name (target), "rhythmbox-library") == 0);
458 		gst_encoding_target_unref (target);
459 
460 		if (is_library) {
461 			/* XXX should provide the option of picking a different format? */
462 			message = g_strdup_printf (_("Additional software is required to encode media "
463 						     "in your preferred format:\n%s"), plugins);
464 		} else {
465 			const char *str;
466 			str = ngettext ("Additional software is required to convert %d file "
467 					"into a format supported by the target device:\n%s",
468 					"Additional software is required to convert %d files "
469 					"into a format supported by the target device:\n%s",
470 					count);
471 			message = g_strdup_printf (str, count, plugins);
472 		}
473 
474 		g_free (plugins);
475 		g_ptr_array_free (descriptions, TRUE);
476 	}
477 
478 	g_object_get (queue->priv->shell, "window", &window, NULL);
479 	dialog = rb_alert_dialog_new (window,
480 				      0,
481 				      GTK_MESSAGE_ERROR,
482 				      GTK_BUTTONS_NONE,
483 				      _("Unable to transfer tracks"),
484 				      message);
485 	g_object_unref (window);
486 	g_free (message);
487 
488 	gtk_dialog_add_button (GTK_DIALOG (dialog), _("_Cancel the transfer"), GTK_RESPONSE_CANCEL);
489 	if (can_continue) {
490 		gtk_dialog_add_button (GTK_DIALOG (dialog), _("_Skip these files"), GTK_RESPONSE_YES);
491 	}
492 	if (profiles != NULL && gst_install_plugins_supported ()) {
493 		gtk_dialog_add_button (GTK_DIALOG (dialog), _("_Install"), GTK_RESPONSE_ACCEPT);
494 	}
495 
496 	rb_alert_dialog_set_details_label (RB_ALERT_DIALOG (dialog), NULL);
497 	g_signal_connect_object (dialog, "response", G_CALLBACK (missing_encoder_response_cb), queue, 0);
498 	gtk_widget_show (dialog);
499 
500 	if (profiles != NULL) {
501 		g_list_free (profiles);
502 	}
503 }
504 
505 /**
506  * rb_track_transfer_queue_start_batch:
507  * @queue: the #RBTrackTransferQueue
508  * @batch: the #RBTrackTransferBatch to add to the queue
509  *
510  * Adds a new transfer batch to the transfer queue; if the queue is currently
511  * empty, the transfer will start immediately, but not before the call returns.
512  */
513 void
rb_track_transfer_queue_start_batch(RBTrackTransferQueue * queue,RBTrackTransferBatch * batch)514 rb_track_transfer_queue_start_batch (RBTrackTransferQueue *queue,
515 				     RBTrackTransferBatch *batch)
516 {
517 	g_queue_push_tail (queue->priv->batch_queue, g_object_ref (batch));
518 	start_next_batch (queue);
519 }
520 
521 /**
522  * rb_track_transfer_queue_cancel_batch:
523  * @queue: the #RBTrackTransferQueue
524  * @batch: the #RBTrackTransferBatch to cancel, or NULL for the current batch
525  *
526  * Removes a transfer batch from the queue.  If an entry from the
527  * batch is currently being transferred, the transfer will be
528  * aborted.
529  */
530 void
rb_track_transfer_queue_cancel_batch(RBTrackTransferQueue * queue,RBTrackTransferBatch * batch)531 rb_track_transfer_queue_cancel_batch (RBTrackTransferQueue *queue,
532 				      RBTrackTransferBatch *batch)
533 {
534 	gboolean found = FALSE;
535 	if (batch == NULL || batch == queue->priv->current) {
536 		batch = queue->priv->current;
537 		queue->priv->current = NULL;
538 		found = TRUE;
539 	} else {
540 		if (g_queue_find (queue->priv->batch_queue, batch)) {
541 			g_queue_remove (queue->priv->batch_queue, batch);
542 			found = TRUE;
543 		}
544 	}
545 
546 	if (found) {
547 		_rb_track_transfer_batch_cancel (batch);
548 		g_object_unref (batch);
549 
550 		start_next_batch (queue);
551 	}
552 }
553 
554 
555 struct FindBatchData
556 {
557 	GList *results;
558 	RBSource *source;
559 };
560 
561 static void
find_batches(RBTrackTransferBatch * batch,struct FindBatchData * data)562 find_batches (RBTrackTransferBatch *batch, struct FindBatchData *data)
563 {
564 	RBSource *src = NULL;
565 	RBSource *dest = NULL;
566 
567 	g_object_get (batch, "source", &src, "destination", &dest, NULL);
568 	if (src == data->source || dest == data->source) {
569 		data->results = g_list_prepend (data->results, batch);
570 	}
571 	g_object_unref (src);
572 	g_object_unref (dest);
573 }
574 
575 /**
576  * rb_track_transfer_queue_find_batch_by_source:
577  * @queue: the #RBTrackTransferQueue
578  * @source: the #RBSource to search for
579  *
580  * Finds all transfer batches where @source is the source or destination.
581  * This should be used to wait for transfers to finish (or cancel them) before
582  * ejecting a device.  The transfer batches are returned in the order they're
583  * found in the queue, so waiting for the @RBTrackTransferBatch::complete signal
584  * on the last one is sufficient to wait for them all to finish.
585  *
586  * Return value: (element-type RBTrackTransferBatch) (transfer container): #GList of #RBTrackTransferBatch objects, not referenced
587  */
588 GList *
rb_track_transfer_queue_find_batch_by_source(RBTrackTransferQueue * queue,RBSource * source)589 rb_track_transfer_queue_find_batch_by_source (RBTrackTransferQueue *queue, RBSource *source)
590 {
591 	struct FindBatchData data;
592 	data.results = NULL;
593 	data.source = source;
594 
595 	/* check the current batch */
596 	if (queue->priv->current != NULL) {
597 		find_batches (queue->priv->current, &data);
598 	}
599 
600 	g_queue_foreach (queue->priv->batch_queue, (GFunc) find_batches, &data);
601 	return data.results;
602 }
603 
604 /**
605  * rb_track_transfer_queue_cancel_for_source:
606  * @queue: the #RBTrackTransferQueue
607  * @source: the #RBSource to cancel transfers to/from
608  *
609  * Cancels all transfers to or from a specified source.
610  */
611 void
rb_track_transfer_queue_cancel_for_source(RBTrackTransferQueue * queue,RBSource * source)612 rb_track_transfer_queue_cancel_for_source (RBTrackTransferQueue *queue, RBSource *source)
613 {
614 	GList *batches;
615 	GList *l;
616 
617 	batches = rb_track_transfer_queue_find_batch_by_source (queue, source);
618 	for (l = batches; l != NULL; l = l->next) {
619 		rb_track_transfer_queue_cancel_batch (queue, l->data);
620 	}
621 	g_list_free (batches);
622 }
623 
624 static void
rb_track_transfer_queue_init(RBTrackTransferQueue * queue)625 rb_track_transfer_queue_init (RBTrackTransferQueue *queue)
626 {
627 	queue->priv = G_TYPE_INSTANCE_GET_PRIVATE (queue,
628 						   RB_TYPE_TRACK_TRANSFER_QUEUE,
629 						   RBTrackTransferQueuePrivate);
630 
631 	queue->priv->batch_queue = g_queue_new ();
632 }
633 
634 
635 static void
impl_set_property(GObject * object,guint prop_id,const GValue * value,GParamSpec * pspec)636 impl_set_property (GObject *object,
637 		   guint prop_id,
638 		   const GValue *value,
639 		   GParamSpec *pspec)
640 {
641 	RBTrackTransferQueue *queue = RB_TRACK_TRANSFER_QUEUE (object);
642 
643 	switch (prop_id) {
644 	case PROP_SHELL:
645 		queue->priv->shell = g_value_get_object (value);
646 		break;
647 	default:
648 		G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
649 		break;
650 	}
651 }
652 
653 static void
impl_get_property(GObject * object,guint prop_id,GValue * value,GParamSpec * pspec)654 impl_get_property (GObject *object,
655 		   guint prop_id,
656 		   GValue *value,
657 		   GParamSpec *pspec)
658 {
659 	RBTrackTransferQueue *queue = RB_TRACK_TRANSFER_QUEUE (object);
660 
661 	switch (prop_id) {
662 	case PROP_SHELL:
663 		g_value_set_object (value, queue->priv->shell);
664 		break;
665 	case PROP_BATCH:
666 		g_value_set_object (value, queue->priv->current);
667 		break;
668 	default:
669 		G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
670 		break;
671 	}
672 }
673 
674 static void
impl_dispose(GObject * object)675 impl_dispose (GObject *object)
676 {
677 	RBTrackTransferQueue *queue = RB_TRACK_TRANSFER_QUEUE (object);
678 
679 	if (queue->priv->current != NULL) {
680 		_rb_track_transfer_batch_cancel (queue->priv->current);
681 		g_object_unref (queue->priv->current);
682 		queue->priv->current = NULL;
683 	}
684 
685 	if (queue->priv->batch_queue != NULL) {
686 		g_queue_foreach (queue->priv->batch_queue, (GFunc) _rb_track_transfer_batch_cancel, NULL);
687 		g_queue_foreach (queue->priv->batch_queue, (GFunc) g_object_unref, NULL);
688 		g_queue_free (queue->priv->batch_queue);
689 	}
690 
691 	if (queue->priv->shell != NULL) {
692 		/* we don't own a reference on the shell. */
693 		queue->priv->shell = NULL;
694 	}
695 
696 	G_OBJECT_CLASS (rb_track_transfer_queue_parent_class)->dispose (object);
697 }
698 
699 static void
rb_track_transfer_queue_class_init(RBTrackTransferQueueClass * klass)700 rb_track_transfer_queue_class_init (RBTrackTransferQueueClass *klass)
701 {
702 	GObjectClass *object_class = G_OBJECT_CLASS (klass);
703 
704 	object_class->set_property = impl_set_property;
705 	object_class->get_property = impl_get_property;
706 	object_class->dispose = impl_dispose;
707 
708 	/**
709 	 * RBTrackTransferQueue:shell:
710 	 *
711 	 * The #RBShell
712 	 */
713 	g_object_class_install_property (object_class,
714 					 PROP_SHELL,
715 					 g_param_spec_object ("shell",
716 							      "shell",
717 							      "the RBShell",
718 							      RB_TYPE_SHELL,
719 							      G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
720 	/**
721 	 * RBTrackTransferQueue:batch:
722 	 *
723 	 * The current #RBTrackTransferBatch being processed
724 	 */
725 	g_object_class_install_property (object_class,
726 					 PROP_BATCH,
727 					 g_param_spec_object ("batch",
728 							      "batch",
729 							      "current RBTrackTransferBatch",
730 							      RB_TYPE_TRACK_TRANSFER_BATCH,
731 							      G_PARAM_READABLE));
732 	/**
733 	 * RBTrackTransferQueue::transfer-progress:
734 	 * @queue: the #RBTrackTransferQueue
735 	 * @done: the number of entries transferred
736 	 * @total: the total number of entries in the batch
737 	 * @fraction: the fraction of the batch that has been transferred
738 	 * @time_left: the estimated remaining time (in seconds)
739 	 *
740 	 * Emitted regularly to convey progress information.  At the end of any given
741 	 * transfer batch, there will be one signal emission with @done == @total and
742 	 * @fraction == 1.0.
743 	 */
744 	signals[TRANSFER_PROGRESS] =
745 		g_signal_new ("transfer-progress",
746 			      RB_TYPE_TRACK_TRANSFER_QUEUE,
747 			      G_SIGNAL_RUN_LAST,
748 			      G_STRUCT_OFFSET (RBTrackTransferQueueClass, transfer_progress),
749 			      NULL, NULL,
750 			      NULL,
751 			      G_TYPE_NONE,
752 			      4, G_TYPE_INT, G_TYPE_INT, G_TYPE_DOUBLE, G_TYPE_INT);
753 	/**
754 	 * RBTrackTransferQueue::missing-plugins:
755 	 * @queue: the #RBTrackTransferQueue
756 	 * @details: the list of plugin detail strings describing the missing plugins
757 	 * @descriptions: the list of descriptions for the missing plugins
758 	 * @closure: a #GClosure to be called when the plugin installation is complete
759 	 *
760 	 * Emitted to request installation of one or more encoder plugins for a
761 	 * destination media format.  When the closure included in the signal args
762 	 * is called, the transfer batch will be started.
763 	 */
764 	signals[MISSING_PLUGINS] =
765 		g_signal_new ("missing-plugins",
766 			      G_OBJECT_CLASS_TYPE (object_class),
767 			      G_SIGNAL_RUN_LAST,
768 			      0,
769 			      NULL, NULL,
770 			      NULL,
771 			      G_TYPE_BOOLEAN,
772 			      3,
773 			      G_TYPE_STRV, G_TYPE_STRV, G_TYPE_CLOSURE);
774 
775 	g_type_class_add_private (klass, sizeof (RBTrackTransferQueuePrivate));
776 }
777