hythmbox-2.98/sources/rb-transfer-target.c

No issues found

  1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*-
  2  *
  3  *  Copyright (C) 2011  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 grant 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
 26  *
 27  */
 28 
 29 #include <config.h>
 30 
 31 #include <stdlib.h>
 32 #include <string.h>
 33 
 34 #include <gst/gst.h>
 35 #include <gst/pbutils/encoding-target.h>
 36 #include <glib/gi18n.h>
 37 
 38 #include <sources/rb-transfer-target.h>
 39 #include <shell/rb-track-transfer-queue.h>
 40 #include <backends/rb-encoder.h>
 41 #include <lib/rb-debug.h>
 42 #include <lib/rb-file-helpers.h>
 43 #include <widgets/rb-dialog.h>
 44 
 45 /* arbitrary length limit for file extensions */
 46 #define EXTENSION_LENGTH_LIMIT	8
 47 
 48 G_DEFINE_INTERFACE (RBTransferTarget, rb_transfer_target, 0)
 49 
 50 /**
 51  * SECTION:rb-transfer-target
 52  * @short_description: interface for sources that can receive track transfers
 53  * @include: rb-transfer-target.h
 54  *
 55  * Sources that can accept track transfers should implement this interface
 56  * and call the associated functions to perform transfers.  The source
 57  * needs to be able to construct target URIs for transfers, and can optionally
 58  * perform its own processing after transfers have finished.  The source
 59  * must also provide a #GstEncodingTarget that describes the formats it
 60  * accepts.
 61  */
 62 
 63 
 64 /**
 65  * rb_transfer_target_build_dest_uri:
 66  * @target: an #RBTransferTarget
 67  * @entry: a #RhythmDBEntry being transferred
 68  * @media_type: destination media type
 69  * @extension: extension associated with destination media type
 70  *
 71  * Constructs a URI to use as the destination for a transfer or transcoding
 72  * operation.  The URI may be on the device itself, if the device is mounted
 73  * into the normal filesystem or through gvfs, or it may be a temporary
 74  * location used to store the file before uploading it to the device.
 75  *
 76  * The destination URI should conform to the device's normal URI format,
 77  * and should use the provided extension instead of the extension from
 78  * the source entry.
 79  *
 80  * Return value: constructed URI
 81  */
 82 char *
 83 rb_transfer_target_build_dest_uri (RBTransferTarget *target,
 84 				   RhythmDBEntry *entry,
 85 				   const char *media_type,
 86 				   const char *extension)
 87 {
 88 	RBTransferTargetInterface *iface = RB_TRANSFER_TARGET_GET_IFACE (target);
 89 	char *uri;
 90 
 91 	uri = iface->build_dest_uri (target, entry, media_type, extension);
 92 	if (uri != NULL) {
 93 		char *sane_uri;
 94 
 95 		sane_uri = rb_sanitize_uri_for_filesystem (uri);
 96 		g_return_val_if_fail (sane_uri != NULL, NULL);
 97 		g_free (uri);
 98 		uri = sane_uri;
 99 
100 		rb_debug ("built dest uri for media type '%s', extension '%s': %s",
101 			  media_type, extension, uri);
102 	} else {
103 		rb_debug ("couldn't build dest uri for media type %s, extension %s",
104 			  media_type, extension);
105 	}
106 
107 	return uri;
108 }
109 
110 /**
111  * rb_transfer_target_track_added:
112  * @target: an #RBTransferTarget
113  * @entry: the source #RhythmDBEntry for the transfer
114  * @uri: the destination URI
115  * @filesize: size of the destination file
116  * @media_type: media type of the destination file
117  *
118  * This is called when a transfer to the target has completed.
119  * If the source's @track_added method returns %TRUE, the destination
120  * URI will be added to the database using the entry type for the device.
121  *
122  * If the target uses a temporary area as the destination for transfers,
123  * it can instead upload the destination file to the device and create an
124  * entry for it, then return %FALSE.
125  */
126 void
127 rb_transfer_target_track_added (RBTransferTarget *target,
128 				RhythmDBEntry *entry,
129 				const char *uri,
130 				guint64 filesize,
131 				const char *media_type)
132 {
133 	RBTransferTargetInterface *iface = RB_TRANSFER_TARGET_GET_IFACE (target);
134 	gboolean add_to_db = TRUE;
135 
136 	if (iface->track_added)
137 		add_to_db = iface->track_added (target, entry, uri, filesize, media_type);
138 
139 	if (add_to_db) {
140 		RhythmDBEntryType *entry_type;
141 		RhythmDB *db;
142 		RBShell *shell;
143 
144 		g_object_get (target, "shell", &shell, NULL);
145 		g_object_get (shell, "db", &db, NULL);
146 		g_object_unref (shell);
147 
148 		g_object_get (target, "entry-type", &entry_type, NULL);
149 		rhythmdb_add_uri_with_types (db, uri, entry_type, NULL, NULL);
150 		g_object_unref (entry_type);
151 
152 		g_object_unref (db);
153 	}
154 }
155 
156 /**
157  * rb_transfer_target_track_add_error:
158  * @target: an #RBTransferTarget
159  * @entry: the source #RhythmDBEntry for the transfer
160  * @uri: the destination URI
161  * @error: the transfer error information
162  *
163  * This is called when a transfer fails.  If the source's
164  * impl_track_add_error implementation returns %TRUE, an error dialog
165  * will be displayed to the user containing the error message, unless
166  * the error indicates that the destination file already exists.
167  */
168 void
169 rb_transfer_target_track_add_error (RBTransferTarget *target,
170 				    RhythmDBEntry *entry,
171 				    const char *uri,
172 				    GError *error)
173 {
174 	RBTransferTargetInterface *iface = RB_TRANSFER_TARGET_GET_IFACE (target);
175 	gboolean show_dialog = TRUE;
176 
177 	/* hrm, want the subclass to decide whether to display the error and
178 	 * whether to cancel the batch (may have some device-specific errors?)
179 	 *
180 	 * for now we'll just cancel on the most common things..
181 	 */
182 	if (iface->track_add_error)
183 		show_dialog = iface->track_add_error (target, entry, uri, error);
184 
185 	if (show_dialog) {
186 		if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_EXISTS)) {
187 			rb_debug ("not displaying 'file exists' error for %s", uri);
188 		} else {
189 			rb_error_dialog (NULL, _("Error transferring track"), "%s", error->message);
190 		}
191 	}
192 }
193 
194 /**
195  * rb_transfer_target_get_format_descriptions:
196  * @target: an #RBTransferTarget
197  *
198  * Returns a #GList of allocated media format descriptions for
199  * the formats supported by the target.  The list and the strings
200  * it holds must be freed by the caller.
201  *
202  * Return value: (element-type utf8) (transfer full): list of descriptions.
203  */
204 GList *
205 rb_transfer_target_get_format_descriptions (RBTransferTarget *target)
206 {
207 	GstEncodingTarget *enctarget;
208 	const GList *l;
209 	GList *desc = NULL;
210 	g_object_get (target, "encoding-target", &enctarget, NULL);
211 	if (enctarget != NULL) {
212 		for (l = gst_encoding_target_get_profiles (enctarget); l != NULL; l = l->next) {
213 			GstEncodingProfile *profile = l->data;
214 			desc = g_list_append (desc, g_strdup (gst_encoding_profile_get_description (profile)));
215 		}
216 		gst_encoding_target_unref (enctarget);
217 	}
218 	return desc;
219 }
220 
221 /**
222  * rb_transfer_target_should_transfer:
223  * @target: an #RBTransferTarget
224  * @entry: a #RhythmDBEntry to consider transferring
225  *
226  * Checks whether @entry should be transferred to the target.
227  * The target can check whether a matching entry already exists on the device,
228  * for instance.  @rb_transfer_target_check_duplicate may form part of
229  * an implementation.  If this method returns %FALSE, the entry
230  * will be skipped.
231  *
232  * Return value: %TRUE if the entry should be transferred to the target
233  */
234 gboolean
235 rb_transfer_target_should_transfer (RBTransferTarget *target, RhythmDBEntry *entry)
236 {
237 	RBTransferTargetInterface *iface = RB_TRANSFER_TARGET_GET_IFACE (target);
238 
239 	return iface->should_transfer (target, entry);
240 }
241 
242 /**
243  * rb_transfer_target_check_category:
244  * @target: an #RBTransferTarget
245  * @entry: a #RhythmDBEntry to check
246  *
247  * This checks that the entry type of @entry is in a suitable
248  * category for transfer.  This can be used to implement
249  * @should_transfer.
250  *
251  * Return value: %TRUE if the entry is in a suitable category
252  */
253 gboolean
254 rb_transfer_target_check_category (RBTransferTarget *target, RhythmDBEntry *entry)
255 {
256 	RhythmDBEntryCategory cat;
257 	RhythmDBEntryType *entry_type;
258 
259 	entry_type = rhythmdb_entry_get_entry_type (entry);
260 	g_object_get (entry_type, "category", &cat, NULL);
261 	return (cat == RHYTHMDB_ENTRY_NORMAL);
262 }
263 
264 /**
265  * rb_transfer_target_check_duplicate:
266  * @target: an #RBTransferTarget
267  * @entry: a #RhythmDBEntry to check
268  *
269  * This checks for an existing entry in the target that matches
270  * the title, album, artist, and track number of the entry being
271  * considered.  This can be used to implement @should_transfer.
272  *
273  * Return value: %TRUE if the entry already exists on the target.
274  */
275 gboolean
276 rb_transfer_target_check_duplicate (RBTransferTarget *target, RhythmDBEntry *entry)
277 {
278 	RhythmDBEntryType *entry_type;
279 	RhythmDB *db;
280 	RBShell *shell;
281 	const char *title;
282 	const char *album;
283 	const char *artist;
284 	gulong track_number;
285 	GtkTreeModel *query_model;
286 	GtkTreeIter iter;
287 	gboolean is_dup;
288 
289 	g_object_get (target, "shell", &shell, "entry-type", &entry_type, NULL);
290 	g_object_get (shell, "db", &db, NULL);
291 	g_object_unref (shell);
292 
293 	query_model = GTK_TREE_MODEL (rhythmdb_query_model_new_empty (db));
294 	title = rhythmdb_entry_get_string (entry, RHYTHMDB_PROP_TITLE);
295 	album = rhythmdb_entry_get_string (entry, RHYTHMDB_PROP_ALBUM);
296 	artist = rhythmdb_entry_get_string (entry, RHYTHMDB_PROP_ARTIST);
297 	track_number = rhythmdb_entry_get_ulong (entry, RHYTHMDB_PROP_TRACK_NUMBER);
298 	rhythmdb_do_full_query (db, RHYTHMDB_QUERY_RESULTS (query_model),
299 				RHYTHMDB_QUERY_PROP_EQUALS,
300 				RHYTHMDB_PROP_TYPE, entry_type,
301 				RHYTHMDB_QUERY_PROP_EQUALS,
302 				RHYTHMDB_PROP_ARTIST, artist,
303 				RHYTHMDB_QUERY_PROP_EQUALS,
304 				RHYTHMDB_PROP_ALBUM, album,
305 				RHYTHMDB_QUERY_PROP_EQUALS,
306 				RHYTHMDB_PROP_TITLE, title,
307 				RHYTHMDB_QUERY_PROP_EQUALS,
308 				RHYTHMDB_PROP_TRACK_NUMBER, track_number,
309 				RHYTHMDB_QUERY_END);
310 
311 	is_dup = gtk_tree_model_get_iter_first (GTK_TREE_MODEL (query_model), &iter);
312 	g_object_unref (entry_type);
313 	g_object_unref (query_model);
314 	g_object_unref (db);
315 	if (is_dup) {
316 		rb_debug ("not transferring %lu - %s - %s - %s as already present",
317 			  track_number, title, album, artist);
318 	}
319 	return is_dup;
320 }
321 
322 static gboolean
323 default_should_transfer (RBTransferTarget *target, RhythmDBEntry *entry)
324 {
325 	if (rb_transfer_target_check_category (target, entry) == FALSE)
326 		return FALSE;
327 
328 	return (rb_transfer_target_check_duplicate (target, entry) == FALSE);
329 }
330 
331 static char *
332 get_dest_uri_cb (RBTrackTransferBatch *batch,
333 		 RhythmDBEntry *entry,
334 		 const char *mediatype,
335 		 const char *extension,
336 		 RBTransferTarget *target)
337 {
338 	char *free_ext = NULL;
339 	char *uri;
340 
341 	/* make sure the extension isn't ludicrously long */
342 	if (extension == NULL) {
343 		extension = "";
344 	} else if (strlen (extension) > EXTENSION_LENGTH_LIMIT) {
345 		free_ext = g_strdup (extension);
346 		free_ext[EXTENSION_LENGTH_LIMIT] = '\0';
347 		extension = free_ext;
348 	}
349 	uri = rb_transfer_target_build_dest_uri (target, entry, mediatype, extension);
350 	g_free (free_ext);
351 	return uri;
352 }
353 
354 static void
355 track_done_cb (RBTrackTransferBatch *batch,
356 	       RhythmDBEntry *entry,
357 	       const char *dest,
358 	       guint64 dest_size,
359 	       const char *dest_mediatype,
360 	       GError *error,
361 	       RBTransferTarget *target)
362 {
363 	if (error != NULL) {
364 		if (g_error_matches (error, RB_ENCODER_ERROR, RB_ENCODER_ERROR_OUT_OF_SPACE) ||
365 		    g_error_matches (error, RB_ENCODER_ERROR, RB_ENCODER_ERROR_DEST_READ_ONLY)) {
366 			rb_debug ("fatal transfer error: %s", error->message);
367 			rb_track_transfer_batch_cancel (batch);
368 		}
369 		rb_transfer_target_track_add_error (target, entry, dest, error);
370 	} else {
371 		rb_transfer_target_track_added (target, entry, dest, dest_size, dest_mediatype);
372 	}
373 }
374 
375 /**
376  * rb_transfer_target_transfer:
377  * @target: an #RBTransferTarget
378  * @entries: a #GList of entries to transfer
379  * @defer: if %TRUE, don't start the transfer until
380  *
381  * Starts tranferring @entries to the target.  This returns the
382  * #RBTrackTransferBatch that it starts, so the caller can track
383  * the progress of the transfer, or NULL if the target doesn't
384  * want any of the entries.
385  *
386  * Return value: (transfer full): an #RBTrackTransferBatch, or NULL
387  */
388 RBTrackTransferBatch *
389 rb_transfer_target_transfer (RBTransferTarget *target, GList *entries, gboolean defer)
390 {
391 	RBTrackTransferQueue *xferq;
392 	RBShell *shell;
393 	GList *l;
394 	RhythmDBEntryType *our_entry_type;
395 	RBTrackTransferBatch *batch;
396 	gboolean start_batch = FALSE;
397 
398 	g_object_get (target,
399 		      "shell", &shell,
400 		      "entry-type", &our_entry_type,
401 		      NULL);
402 	g_object_get (shell, "track-transfer-queue", &xferq, NULL);
403 	g_object_unref (shell);
404 
405 	batch = g_object_steal_data (G_OBJECT (target), "transfer-target-batch");
406 
407 	if (batch == NULL) {
408 		batch = rb_track_transfer_batch_new (NULL, NULL, G_OBJECT (target));
409 
410 		g_signal_connect_object (batch, "get-dest-uri", G_CALLBACK (get_dest_uri_cb), target, 0);
411 		g_signal_connect_object (batch, "track-done", G_CALLBACK (track_done_cb), target, 0);
412 	} else {
413 		start_batch = TRUE;
414 	}
415 
416 	for (l = entries; l != NULL; l = l->next) {
417 		RhythmDBEntry *entry;
418 		RhythmDBEntryType *entry_type;
419 		const char *location;
420 
421 		entry = (RhythmDBEntry *)l->data;
422 		location = rhythmdb_entry_get_string (entry, RHYTHMDB_PROP_LOCATION);
423 		entry_type = rhythmdb_entry_get_entry_type (entry);
424 
425 		if (entry_type != our_entry_type) {
426 			if (rb_transfer_target_should_transfer (target, entry)) {
427 				rb_debug ("pasting entry %s", location);
428 				rb_track_transfer_batch_add (batch, entry);
429 				start_batch = TRUE;
430 			} else {
431 				rb_debug ("target doesn't want entry %s", location);
432 			}
433 		} else {
434 			rb_debug ("can't copy entry %s from the target to itself", location);
435 		}
436 	}
437 	g_object_unref (our_entry_type);
438 
439 	if (start_batch) {
440 		if (defer) {
441 			g_object_set_data_full (G_OBJECT (target), "transfer-target-batch", g_object_ref (batch), g_object_unref);
442 		} else {
443 			GstEncodingTarget *encoding_target;
444 			g_object_get (target, "encoding-target", &encoding_target, NULL);
445 			g_object_set (batch, "encoding-target", encoding_target, NULL);
446 			gst_encoding_target_unref (encoding_target);
447 
448 			rb_track_transfer_queue_start_batch (xferq, batch);
449 		}
450 	} else {
451 		g_object_unref (batch);
452 		batch = NULL;
453 	}
454 	g_object_unref (xferq);
455 	return batch;
456 }
457 
458 
459 static void
460 rb_transfer_target_default_init (RBTransferTargetInterface *interface)
461 {
462 	interface->should_transfer = default_should_transfer;
463 
464 	g_object_interface_install_property (interface,
465 					     gst_param_spec_mini_object ("encoding-target",
466 									 "encoding target",
467 									 "GstEncodingTarget",
468 									 GST_TYPE_ENCODING_TARGET,
469 									 G_PARAM_READWRITE));
470 }