[rhythmbox] mtp: huge rewrite to do all device operations in a separate thread
- From: Jonathan Matthew <jmatthew src gnome org>
- To: svn-commits-list gnome org
- Cc:
- Subject: [rhythmbox] mtp: huge rewrite to do all device operations in a separate thread
- Date: Sat, 10 Oct 2009 05:23:33 +0000 (UTC)
commit c5c82a696448d53b16be94d774f5628f73371ddb
Author: Jonathan Matthew <jonathan d14n org>
Date: Sat Oct 10 15:16:47 2009 +1000
mtp: huge rewrite to do all device operations in a separate thread
This moves all MTP device operations to a separate thread, including
uploads and downloads, so the main UI thread never gets blocked.
Noteworthy things: the source object is now created based on a raw
device, so it has to open the device. The source destroys itself if the
device cannot be opened.
There is now a GStreamer MTP sink element that writes data to a
temporary file, then uploads the file when it receives an EOS event.
Fixes bug #534981.
plugins/mtpdevice/Makefile.am | 5 +-
plugins/mtpdevice/rb-mtp-gst-sink.c | 475 ++++++++++++++++++++
plugins/mtpdevice/rb-mtp-gst-src.c | 174 ++++----
plugins/mtpdevice/rb-mtp-plugin.c | 246 +++++------
plugins/mtpdevice/rb-mtp-source.c | 776 +++++++++++++++------------------
plugins/mtpdevice/rb-mtp-source.h | 4 +-
plugins/mtpdevice/rb-mtp-thread.c | 809 +++++++++++++++++++++++++++++++++++
plugins/mtpdevice/rb-mtp-thread.h | 125 ++++++
po/POTFILES.in | 1 +
9 files changed, 1964 insertions(+), 651 deletions(-)
---
diff --git a/plugins/mtpdevice/Makefile.am b/plugins/mtpdevice/Makefile.am
index 71175aa..151d231 100644
--- a/plugins/mtpdevice/Makefile.am
+++ b/plugins/mtpdevice/Makefile.am
@@ -6,8 +6,11 @@ plugin_LTLIBRARIES = libmtpdevice.la
libmtpdevice_la_SOURCES = \
rb-mtp-plugin.c \
rb-mtp-gst-src.c \
+ rb-mtp-gst-sink.c \
rb-mtp-source.c \
- rb-mtp-source.h
+ rb-mtp-source.h \
+ rb-mtp-thread.c \
+ rb-mtp-thread.h
libmtpdevice_la_LDFLAGS = $(PLUGIN_LIBTOOL_FLAGS)
libmtpdevice_la_LIBTOOLFLAGS = --tag=disable-static
diff --git a/plugins/mtpdevice/rb-mtp-gst-sink.c b/plugins/mtpdevice/rb-mtp-gst-sink.c
new file mode 100644
index 0000000..e710e20
--- /dev/null
+++ b/plugins/mtpdevice/rb-mtp-gst-sink.c
@@ -0,0 +1,475 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*-
+ *
+ * Copyright (C) 2009 Jonathan Matthew <jonathan d14n org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * The Rhythmbox authors hereby grant permission for non-GPL compatible
+ * GStreamer plugins to be used and distributed together with GStreamer
+ * and Rhythmbox. This permission is above and beyond the permissions granted
+ * by the GPL license by which Rhythmbox is covered. If you modify this code
+ * you may extend this exception to your version of the code, but you are not
+ * obligated to do so. If you do not wish to do so, delete this exception
+ * statement from your version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ */
+
+#include "config.h"
+
+#include <string.h>
+
+#include <glib/gi18n.h>
+#include <libmtp.h>
+#include <gst/gst.h>
+
+#include "rb-mtp-thread.h"
+#include "rb-debug.h"
+#include "rb-file-helpers.h"
+
+#define RB_TYPE_MTP_SINK (rb_mtp_sink_get_type())
+#define RB_MTP_SINK(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj),RB_TYPE_MTP_SINK,RBMTPSink))
+#define RB_MTP_SINK_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST((klass),RB_TYPE_MTP_SINK,RBMTPSinkClass))
+#define RB_IS_MTP_SINK(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj),RB_TYPE_MTP_SINK))
+#define RB_IS_MTP_SINK_CLASS(obj) (G_TYPE_CHECK_CLASS_TYPE((klass),RB_TYPE_MTP_SINK))
+
+typedef struct _RBMTPSink RBMTPSink;
+typedef struct _RBMTPSinkClass RBMTPSinkClass;
+
+struct _RBMTPSink
+{
+ GstBin parent;
+
+ RBMtpThread *device_thread;
+
+ LIBMTP_track_t *track;
+ char *tempfile;
+
+ GstElement *fdsink;
+ GstPad *ghostpad;
+
+ GError *upload_error;
+ GMutex *upload_mutex;
+ GCond *upload_cond;
+ gboolean upload_done;
+};
+
+struct _RBMTPSinkClass
+{
+ GstBinClass parent_class;
+};
+
+enum
+{
+ PROP_0,
+ PROP_URI,
+ PROP_MTP_TRACK,
+ PROP_DEVICE_THREAD
+};
+
+static GstStaticPadTemplate sinktemplate = GST_STATIC_PAD_TEMPLATE ("sink",
+ GST_PAD_SINK,
+ GST_PAD_ALWAYS,
+ GST_STATIC_CAPS_ANY);
+
+static GstElementDetails rb_mtp_sink_details =
+GST_ELEMENT_DETAILS ("RB MTP Sink",
+ "Sink/File",
+ "Uploads tracks to MTP devices",
+ "Jonathan Matthew <jonathan d14n org>");
+
+static void rb_mtp_sink_uri_handler_init (gpointer g_iface, gpointer iface_data);
+
+static void
+_do_init (GType mtp_sink_type)
+{
+ static const GInterfaceInfo urihandler_info = {
+ rb_mtp_sink_uri_handler_init,
+ NULL,
+ NULL
+ };
+
+ g_type_add_interface_static (mtp_sink_type, GST_TYPE_URI_HANDLER,
+ &urihandler_info);
+}
+
+GST_BOILERPLATE_FULL (RBMTPSink, rb_mtp_sink, GstBin, GST_TYPE_BIN, _do_init);
+
+static void
+rb_mtp_sink_base_init (gpointer g_class)
+{
+ GstElementClass *element_class = GST_ELEMENT_CLASS (g_class);
+ gst_element_class_add_pad_template (element_class,
+ gst_static_pad_template_get (&sinktemplate));
+ gst_element_class_set_details (element_class, &rb_mtp_sink_details);
+}
+
+static void
+rb_mtp_sink_init (RBMTPSink *sink, RBMTPSinkClass *klass)
+{
+ GstPad *pad;
+
+ sink->upload_mutex = g_mutex_new ();
+ sink->upload_cond = g_cond_new ();
+
+ /* create actual sink */
+ sink->fdsink = gst_element_factory_make ("fdsink", NULL);
+ if (sink->fdsink == NULL) {
+ g_warning ("couldn't create fdsink element");
+ return;
+ }
+
+ gst_bin_add (GST_BIN (sink), sink->fdsink);
+ gst_object_ref (sink->fdsink);
+
+ /* create ghost pad */
+ pad = gst_element_get_pad (sink->fdsink, "sink");
+ sink->ghostpad = gst_ghost_pad_new ("sink", pad);
+ gst_element_add_pad (GST_ELEMENT (sink), sink->ghostpad);
+ gst_object_ref (sink->ghostpad);
+ gst_object_unref (pad);
+
+}
+
+static GstStateChangeReturn
+rb_mtp_sink_open_tempfile (RBMTPSink *sink)
+{
+ int fd;
+ GError *tmperror = NULL;
+
+ fd = g_file_open_tmp ("rb-mtp-temp-XXXXXX", &sink->tempfile, &tmperror);
+ if (fd == -1) {
+ GST_ELEMENT_ERROR (sink, RESOURCE, OPEN_WRITE, (_("Unable to open temporary file: %s"), tmperror->message), NULL);
+ return GST_STATE_CHANGE_FAILURE;
+ }
+ rb_debug ("opened temporary file %s", sink->tempfile);
+
+ g_object_set (sink->fdsink, "fd", fd, "sync", FALSE, NULL);
+ return GST_STATE_CHANGE_SUCCESS;
+}
+
+static GstStateChangeReturn
+rb_mtp_sink_close_tempfile (RBMTPSink *sink)
+{
+ if (sink->tempfile != NULL) {
+ rb_debug ("deleting tempfile %s", sink->tempfile);
+ remove (sink->tempfile);
+ g_free (sink->tempfile);
+ sink->tempfile = NULL;
+ }
+
+ return GST_STATE_CHANGE_SUCCESS;
+}
+
+static void
+upload_callback (LIBMTP_track_t *track, GError *error, RBMTPSink *sink)
+{
+ rb_debug ("mtp upload callback for %s: item ID %d", track->filename, track->item_id);
+ g_mutex_lock (sink->upload_mutex);
+
+ if (error != NULL) {
+ sink->upload_error = g_error_copy (error);
+ }
+ sink->upload_done = TRUE;
+
+ g_cond_signal (sink->upload_cond);
+ g_mutex_unlock (sink->upload_mutex);
+}
+
+static void
+rb_mtp_sink_handle_message (GstBin *bin, GstMessage *message)
+{
+ /* when we get an EOS message from the fdsink, close the fd and upload the
+ * file to the device.
+ */
+ if (GST_MESSAGE_TYPE (message) == GST_MESSAGE_EOS) {
+ int fd;
+ struct stat stat_buf;
+
+ RBMTPSink *sink = RB_MTP_SINK (bin);
+
+ /* fill in the file size and close the fd */
+ g_object_get (sink->fdsink, "fd", &fd, NULL);
+ fstat (fd, &stat_buf);
+ sink->track->filesize = stat_buf.st_size;
+ close (fd);
+
+ rb_debug ("handling EOS from fdsink; file size is %" G_GUINT64_FORMAT, sink->track->filesize);
+
+ /* start the upload, then wait for it to finish.
+ * we're on a streaming thread here, so blocking is no problem.
+ */
+ g_mutex_lock (sink->upload_mutex);
+
+ sink->upload_done = FALSE;
+ rb_mtp_thread_upload_track (sink->device_thread,
+ sink->track,
+ sink->tempfile,
+ (RBMtpUploadCallback) upload_callback,
+ g_object_ref (sink),
+ g_object_unref);
+
+ while (sink->upload_done == FALSE) {
+ g_cond_wait (sink->upload_cond, sink->upload_mutex);
+ }
+ g_mutex_unlock (sink->upload_mutex);
+
+ /* post error message if the upload failed - this should get there before
+ * this EOS message does, so it should work OK.
+ */
+ if (sink->upload_error != NULL) {
+ int code;
+
+ switch (sink->upload_error->code) {
+ case RB_MTP_THREAD_ERROR_NO_SPACE:
+ code = GST_RESOURCE_ERROR_NO_SPACE_LEFT;
+ break;
+
+ default:
+ case RB_MTP_THREAD_ERROR_SEND_TRACK:
+ code = GST_RESOURCE_ERROR_WRITE;
+ break;
+ }
+
+ GST_WARNING_OBJECT (sink, "error: %s", sink->upload_error->message);
+ gst_element_message_full (GST_ELEMENT (sink),
+ GST_MESSAGE_ERROR,
+ GST_RESOURCE_ERROR, code,
+ sink->upload_error->message, NULL,
+ __FILE__, GST_FUNCTION, __LINE__);
+ }
+ }
+
+ GST_BIN_CLASS (parent_class)->handle_message (bin, message);
+}
+
+static GstStateChangeReturn
+rb_mtp_sink_change_state (GstElement *element, GstStateChange transition)
+{
+ GstStateChangeReturn ret = GST_STATE_CHANGE_SUCCESS;
+ RBMTPSink *sink = RB_MTP_SINK (element);
+
+ switch (transition) {
+ case GST_STATE_CHANGE_NULL_TO_READY:
+ ret = rb_mtp_sink_open_tempfile (sink);
+ if (ret != GST_STATE_CHANGE_SUCCESS)
+ return ret;
+ break;
+
+ case GST_STATE_CHANGE_READY_TO_PAUSED:
+ break;
+
+ case GST_STATE_CHANGE_PAUSED_TO_PLAYING:
+ break;
+
+ default:
+ break;
+ }
+
+ ret = GST_ELEMENT_CLASS (parent_class)->change_state (element, transition);
+
+ switch (transition) {
+ case GST_STATE_CHANGE_PLAYING_TO_PAUSED:
+ break;
+ case GST_STATE_CHANGE_PAUSED_TO_READY:
+ break;
+ case GST_STATE_CHANGE_READY_TO_NULL:
+ ret = rb_mtp_sink_close_tempfile (sink);
+ break;
+ default:
+ break;
+ }
+
+ return ret;
+}
+
+static void
+rb_mtp_sink_set_property (GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec)
+{
+ RBMTPSink *sink = RB_MTP_SINK (object);
+
+ switch (prop_id) {
+ case PROP_MTP_TRACK:
+ sink->track = g_value_get_pointer (value);
+ break;
+ case PROP_DEVICE_THREAD:
+ sink->device_thread = g_value_dup_object (value);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+}
+
+static void
+rb_mtp_sink_get_property (GObject *object, guint prop_id, GValue *value, GParamSpec *pspec)
+{
+ RBMTPSink *sink = RB_MTP_SINK (object);
+
+ switch (prop_id) {
+ case PROP_MTP_TRACK:
+ g_value_set_pointer (value, sink->track);
+ break;
+ case PROP_DEVICE_THREAD:
+ g_value_set_object (value, sink->device_thread);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+}
+
+static void
+rb_mtp_sink_dispose (GObject *object)
+{
+ RBMTPSink *sink;
+ sink = RB_MTP_SINK (object);
+
+ if (sink->ghostpad) {
+ gst_object_unref (sink->ghostpad);
+ sink->ghostpad = NULL;
+ }
+
+ if (sink->fdsink) {
+ gst_object_unref (sink->fdsink);
+ sink->fdsink = NULL;
+ }
+
+ if (sink->device_thread) {
+ g_object_unref (sink->device_thread);
+ sink->device_thread = NULL;
+ }
+
+ G_OBJECT_CLASS (parent_class)->dispose (object);
+}
+
+static void
+rb_mtp_sink_finalize (GObject *object)
+{
+ RBMTPSink *sink;
+ sink = RB_MTP_SINK (object);
+
+ g_mutex_free (sink->upload_mutex);
+ g_cond_free (sink->upload_cond);
+
+ /* probably isn't right */
+ if (sink->upload_error) {
+ g_error_free (sink->upload_error);
+ }
+
+ G_OBJECT_CLASS (parent_class)->finalize (object);
+}
+
+static void
+rb_mtp_sink_class_init (RBMTPSinkClass *klass)
+{
+ GObjectClass *gobject_class;
+ GstElementClass *element_class;
+ GstBinClass *bin_class;
+
+ gobject_class = G_OBJECT_CLASS (klass);
+ gobject_class->dispose = rb_mtp_sink_dispose;
+ gobject_class->finalize = rb_mtp_sink_finalize;
+ gobject_class->set_property = rb_mtp_sink_set_property;
+ gobject_class->get_property = rb_mtp_sink_get_property;
+
+ element_class = GST_ELEMENT_CLASS (klass);
+ element_class->change_state = rb_mtp_sink_change_state;
+
+ bin_class = GST_BIN_CLASS (klass);
+ bin_class->handle_message = rb_mtp_sink_handle_message;
+
+ g_object_class_install_property (gobject_class,
+ PROP_MTP_TRACK,
+ g_param_spec_pointer ("mtp-track",
+ "libmtp track",
+ "libmtp track",
+ G_PARAM_READWRITE));
+ g_object_class_install_property (gobject_class,
+ PROP_DEVICE_THREAD,
+ g_param_spec_object ("device-thread",
+ "device-thread",
+ "device handling thread",
+ RB_TYPE_MTP_THREAD,
+ G_PARAM_READWRITE));
+}
+
+
+/* URI handler interface */
+
+static guint
+rb_mtp_sink_uri_get_type (void)
+{
+ return GST_URI_SINK;
+}
+
+static gchar **
+rb_mtp_sink_uri_get_protocols (void)
+{
+ static gchar *protocols[] = {"xrbmtp", NULL};
+ return protocols;
+}
+
+static const gchar *
+rb_mtp_sink_uri_get_uri (GstURIHandler *handler)
+{
+ /* more or less */
+ return "xrbmtp://";
+}
+
+static gboolean
+rb_mtp_sink_uri_set_uri (GstURIHandler *handler, const gchar *uri)
+{
+ RBMTPSink *sink = RB_MTP_SINK (handler);
+
+ if (GST_STATE (sink) == GST_STATE_PLAYING || GST_STATE (sink) == GST_STATE_PAUSED) {
+ return FALSE;
+ }
+
+ if (g_str_has_prefix (uri, "xrbmtp://") == FALSE) {
+ return FALSE;
+ }
+
+ /* URI doesn't actually contain any information, it all comes from the track */
+
+ return TRUE;
+}
+
+static void
+rb_mtp_sink_uri_handler_init (gpointer g_iface, gpointer iface_data)
+{
+ GstURIHandlerInterface *iface = (GstURIHandlerInterface *) g_iface;
+
+ iface->get_type = rb_mtp_sink_uri_get_type;
+ iface->get_protocols = rb_mtp_sink_uri_get_protocols;
+ iface->get_uri = rb_mtp_sink_uri_get_uri;
+ iface->set_uri = rb_mtp_sink_uri_set_uri;
+}
+
+static gboolean
+plugin_init (GstPlugin *plugin)
+{
+ gboolean ret = gst_element_register (plugin, "rbmtpsink", GST_RANK_PRIMARY, RB_TYPE_MTP_SINK);
+ return ret;
+}
+
+GST_PLUGIN_DEFINE_STATIC (GST_VERSION_MAJOR,
+ GST_VERSION_MINOR,
+ "rbmtpsink",
+ "element to upload files to MTP devices",
+ plugin_init,
+ VERSION,
+ "GPL",
+ PACKAGE,
+ "");
diff --git a/plugins/mtpdevice/rb-mtp-gst-src.c b/plugins/mtpdevice/rb-mtp-gst-src.c
index f7f227d..9430059 100644
--- a/plugins/mtpdevice/rb-mtp-gst-src.c
+++ b/plugins/mtpdevice/rb-mtp-gst-src.c
@@ -34,6 +34,7 @@
#include <libmtp.h>
#include <gst/gst.h>
+#include "rb-mtp-thread.h"
#include "rb-debug.h"
#include "rb-file-helpers.h"
@@ -50,7 +51,7 @@ struct _RBMTPSrc
{
GstBin parent;
- LIBMTP_mtpdevice_t *device;
+ RBMtpThread *device_thread;
char *track_uri;
uint32_t track_id;
@@ -58,6 +59,11 @@ struct _RBMTPSrc
GstElement *filesrc;
GstPad *ghostpad;
+
+ GError *download_error;
+ GMutex *download_mutex;
+ GCond *download_cond;
+ GstStateChangeReturn download_result;
};
struct _RBMTPSrcClass
@@ -69,7 +75,7 @@ enum
{
PROP_0,
PROP_URI,
- PROP_DEVICE
+ PROP_DEVICE_THREAD
};
static GstStaticPadTemplate srctemplate = GST_STATIC_PAD_TEMPLATE ("src",
@@ -115,6 +121,9 @@ rb_mtp_src_init (RBMTPSrc *src, RBMTPSrcClass *klass)
{
GstPad *pad;
+ src->download_mutex = g_mutex_new ();
+ src->download_cond = g_cond_new ();
+
/* create actual source */
src->filesrc = gst_element_factory_make ("filesrc", NULL);
if (src->filesrc == NULL) {
@@ -152,85 +161,65 @@ rb_mtp_src_set_uri (RBMTPSrc *src, const char *uri)
return TRUE;
}
+static void
+download_cb (LIBMTP_track_t *track, const char *filename, GError *error, RBMTPSrc *src)
+{
+ rb_debug ("mtp download callback for %s: %s", filename, error ? error->message : "OK");
+ g_mutex_lock (src->download_mutex);
+
+ if (filename == NULL) {
+ src->download_error = g_error_copy (error);
+ src->download_result = GST_STATE_CHANGE_FAILURE;
+ } else {
+ src->download_result = GST_STATE_CHANGE_SUCCESS;
+ src->tempfile = g_strdup (filename);
+ }
+
+ g_cond_signal (src->download_cond);
+ g_mutex_unlock (src->download_mutex);
+}
+
static GstStateChangeReturn
rb_mtp_src_get_file (RBMTPSrc *src)
{
- LIBMTP_file_t *fileinfo;
- GFile *dir;
- GError *error;
- char *tempfile;
- int fd;
- gboolean check;
- int mtpret;
-
- /* get file info */
- fileinfo = LIBMTP_Get_Filemetadata (src->device, src->track_id);
- if (fileinfo == NULL) {
- rb_debug ("unable to get mtp file metadata");
- LIBMTP_error_t *stack;
-
- stack = LIBMTP_Get_Errorstack (src->device);
- GST_ELEMENT_ERROR(src, RESOURCE, READ,
- (_("Unable to copy file from MTP device: %s"), stack->error_text),
- (NULL));
- LIBMTP_Clear_Errorstack (src->device);
- }
+ g_mutex_lock (src->download_mutex);
+ src->download_result = GST_STATE_CHANGE_ASYNC;
+ rb_mtp_thread_download_track (src->device_thread, src->track_id, "", (RBMtpDownloadCallback)download_cb, g_object_ref (src), g_object_unref);
- /* check for free space */
- dir = g_file_new_for_path (g_get_tmp_dir ());
- rb_debug ("checking we've got %" G_GUINT64_FORMAT " bytes available in %s",
- fileinfo->filesize,
- g_get_tmp_dir ());
- check = rb_check_dir_has_space (dir, fileinfo->filesize);
- LIBMTP_destroy_file_t (fileinfo);
- g_object_unref (dir);
- if (check == FALSE) {
- rb_debug ("not enough space to copy track from MTP device");
- GST_ELEMENT_ERROR(src, RESOURCE, NO_SPACE_LEFT,
- (_("Not enough space in %s"), g_get_tmp_dir ()),
- (NULL));
- g_object_unref (dir);
- LIBMTP_destroy_file_t (fileinfo);
- return GST_STATE_CHANGE_FAILURE;
+ while (src->download_result == GST_STATE_CHANGE_ASYNC) {
+ g_cond_wait (src->download_cond, src->download_mutex);
}
+ g_mutex_unlock (src->download_mutex);
+ rb_debug ("download completed, state change return %s", gst_element_state_change_return_get_name (src->download_result));
+
+ if (src->download_error) {
+ int code;
+ switch (src->download_error->code) {
+ case RB_MTP_THREAD_ERROR_NO_SPACE:
+ code = GST_RESOURCE_ERROR_NO_SPACE_LEFT;
+ break;
- /* open temporary file */
- fd = g_file_open_tmp ("rb-mtp-temp-XXXXXX", &tempfile, &error);
- if (fd == -1) {
- rb_debug ("failed to open temporary file");
- GST_ELEMENT_ERROR(src, RESOURCE, OPEN_READ_WRITE,
- (_("Unable to open temporary file: %s"), error->message), (NULL));
- g_error_free (error);
- return GST_STATE_CHANGE_FAILURE;
- }
- rb_debug ("created temporary file %s", tempfile);
-
- /* copy file from MTP device */
- mtpret = LIBMTP_Get_Track_To_File_Descriptor (src->device, src->track_id, fd, NULL, NULL);
- if (mtpret != 0) {
- rb_debug ("failed to copy file from MTP device");
- LIBMTP_error_t *stack;
-
- stack = LIBMTP_Get_Errorstack (src->device);
- GST_ELEMENT_ERROR(src, RESOURCE, READ,
- (_("Unable to copy file from MTP device: %s"), stack->error_text),
- (NULL));
- LIBMTP_Clear_Errorstack (src->device);
-
- close (fd);
- remove (tempfile);
- g_free (tempfile);
- return GST_STATE_CHANGE_FAILURE;
- }
+ case RB_MTP_THREAD_ERROR_TEMPFILE:
+ code = GST_RESOURCE_ERROR_OPEN_WRITE;
+ break;
- rb_debug ("copied file from mtp device");
+ default:
+ case RB_MTP_THREAD_ERROR_GET_TRACK:
+ code = GST_RESOURCE_ERROR_READ;
+ break;
- close (fd);
- src->tempfile = tempfile;
+ }
- /* point the filesrc at the file */
- g_object_set (src->filesrc, "location", src->tempfile, NULL);
- return GST_STATE_CHANGE_SUCCESS;
+ GST_WARNING_OBJECT (src, "error: %s", src->download_error->message);
+ gst_element_message_full (GST_ELEMENT (src),
+ GST_MESSAGE_ERROR,
+ GST_RESOURCE_ERROR, code,
+ src->download_error->message, NULL,
+ __FILE__, GST_FUNCTION, __LINE__);
+ } else if (src->download_result == GST_STATE_CHANGE_SUCCESS) {
+ g_object_set (src->filesrc, "location", src->tempfile, NULL);
+ }
+ return src->download_result;
}
static GstStateChangeReturn
@@ -295,8 +284,8 @@ rb_mtp_src_set_property (GObject *object, guint prop_id, const GValue *value, GP
case PROP_URI:
rb_mtp_src_set_uri (src, g_value_get_string (value));
break;
- case PROP_DEVICE:
- src->device = g_value_get_pointer (value);
+ case PROP_DEVICE_THREAD:
+ src->device_thread = g_value_dup_object (value);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
@@ -313,8 +302,8 @@ rb_mtp_src_get_property (GObject *object, guint prop_id, GValue *value, GParamSp
case PROP_URI:
g_value_set_string (value, src->track_uri);
break;
- case PROP_DEVICE:
- g_value_set_pointer (value, src->device);
+ case PROP_DEVICE_THREAD:
+ g_value_set_object (value, src->device_thread);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
@@ -338,10 +327,31 @@ rb_mtp_src_dispose (GObject *object)
src->filesrc = NULL;
}
+ if (src->device_thread) {
+ g_object_unref (src->device_thread);
+ src->device_thread = NULL;
+ }
+
G_OBJECT_CLASS (parent_class)->dispose (object);
}
static void
+rb_mtp_src_finalize (GObject *object)
+{
+ RBMTPSrc *src;
+ src = RB_MTP_SRC (object);
+
+ g_mutex_free (src->download_mutex);
+ g_cond_free (src->download_cond);
+
+ if (src->download_error) {
+ g_error_free (src->download_error);
+ }
+
+ G_OBJECT_CLASS (parent_class)->finalize (object);
+}
+
+static void
rb_mtp_src_class_init (RBMTPSrcClass *klass)
{
GObjectClass *gobject_class;
@@ -349,6 +359,7 @@ rb_mtp_src_class_init (RBMTPSrcClass *klass)
gobject_class = G_OBJECT_CLASS (klass);
gobject_class->dispose = rb_mtp_src_dispose;
+ gobject_class->finalize = rb_mtp_src_finalize;
gobject_class->set_property = rb_mtp_src_set_property;
gobject_class->get_property = rb_mtp_src_get_property;
@@ -363,11 +374,12 @@ rb_mtp_src_class_init (RBMTPSrcClass *klass)
NULL,
G_PARAM_READWRITE));
g_object_class_install_property (gobject_class,
- PROP_DEVICE,
- g_param_spec_pointer ("device",
- "device",
- "libmtp device",
- G_PARAM_READWRITE));
+ PROP_DEVICE_THREAD,
+ g_param_spec_object ("device-thread",
+ "device-thread",
+ "device handling thread",
+ RB_TYPE_MTP_THREAD,
+ G_PARAM_READWRITE));
}
diff --git a/plugins/mtpdevice/rb-mtp-plugin.c b/plugins/mtpdevice/rb-mtp-plugin.c
index c1d7053..1a7cf3d 100644
--- a/plugins/mtpdevice/rb-mtp-plugin.c
+++ b/plugins/mtpdevice/rb-mtp-plugin.c
@@ -103,15 +103,16 @@ static void impl_deactivate (RBPlugin *plugin, RBShell *shell);
#if defined(HAVE_GUDEV)
static RBSource* create_source_device_cb (RBRemovableMediaManager *rmm, GObject *device, RBMtpPlugin *plugin);
#else
+static void rb_mtp_plugin_maybe_add_source (RBMtpPlugin *plugin, const char *udi, LIBMTP_raw_device_t *raw_devices, int num);
static void rb_mtp_plugin_device_added (LibHalContext *context, const char *udi);
static void rb_mtp_plugin_device_removed (LibHalContext *context, const char *udi);
static gboolean rb_mtp_plugin_setup_dbus_hal_connection (RBMtpPlugin *plugin);
-static RBSource* create_source_cb (RBMtpPlugin *plugin, LIBMTP_mtpdevice_t *device, const char *udi);
#endif
static void rb_mtp_plugin_eject (GtkAction *action, RBMtpPlugin *plugin);
static void rb_mtp_plugin_rename (GtkAction *action, RBMtpPlugin *plugin);
GType rb_mtp_src_get_type (void);
+GType rb_mtp_sink_get_type (void);
RB_PLUGIN_REGISTER(RBMtpPlugin, rb_mtp_plugin)
@@ -139,8 +140,9 @@ rb_mtp_plugin_class_init (RBMtpPluginClass *klass)
/* register types used by the plugin */
RB_PLUGIN_REGISTER_TYPE (rb_mtp_source);
- /* ensure the gstreamer src element gets linked in */
+ /* ensure the gstreamer elements get linked in */
rb_mtp_src_get_type ();
+ rb_mtp_sink_get_type ();
}
static void
@@ -168,10 +170,8 @@ impl_activate (RBPlugin *bplugin, RBShell *shell)
#if defined(HAVE_GUDEV)
gboolean rmm_scanned = FALSE;
#else
- int num, i, ret;
- char **devices;
- LIBMTP_device_entry_t *entries;
- int numentries;
+ int num_mtp_devices;
+ LIBMTP_raw_device_t *mtp_devices;
#endif
plugin->shell = shell;
@@ -214,39 +214,25 @@ impl_activate (RBPlugin *bplugin, RBShell *shell)
}
rb_profile_start ("scanning for MTP devices");
- devices = libhal_get_all_devices (plugin->hal_context, &num, NULL);
- ret = LIBMTP_Get_Supported_Devices_List (&entries, &numentries);
- if (ret == 0) {
-
- for (i = 0; i < num; i++) {
- int vendor_id;
- int product_id;
- const char *tmpudi;
- int p;
-
- tmpudi = devices[i];
- vendor_id = libhal_device_get_property_int (plugin->hal_context, tmpudi, "usb.vendor_id", NULL);
- product_id = libhal_device_get_property_int (plugin->hal_context, tmpudi, "usb.product_id", NULL);
- for (p = 0; p < numentries; p++) {
-
- if (entries[p].vendor_id == vendor_id && entries[p].product_id == product_id) {
- LIBMTP_mtpdevice_t *device = LIBMTP_Get_First_Device ();
- if (device != NULL) {
- create_source_cb (plugin, device, tmpudi);
- break;
- } else {
- rb_debug ("error, could not get a hold on the device. Reset and Restart");
- }
- }
- }
+ LIBMTP_Detect_Raw_Devices (&mtp_devices, &num_mtp_devices);
+ if (num_mtp_devices > 0) {
+ int num_hal_devices;
+ char **hal_devices;
+ int i;
+
+ rb_debug ("%d MTP devices found", num_mtp_devices);
+
+ hal_devices = libhal_get_all_devices (plugin->hal_context, &num_hal_devices, NULL);
+ for (i = 0; i < num_hal_devices; i++) {
+ /* should narrow this down a bit - usb only, for a start */
+ rb_mtp_plugin_maybe_add_source (plugin, hal_devices[i], mtp_devices, num_mtp_devices);
}
- } else {
- rb_debug ("Couldn't list mtp devices");
+ libhal_free_string_array (hal_devices);
+ }
+ if (mtp_devices != NULL) {
+ free (mtp_devices);
}
-
- libhal_free_string_array (devices);
rb_profile_end ("scanning for MTP devices");
-
#endif
g_object_unref (rmm);
@@ -296,12 +282,61 @@ impl_deactivate (RBPlugin *bplugin, RBShell *shell)
}
static void
+rb_mtp_plugin_eject (GtkAction *action, RBMtpPlugin *plugin)
+{
+ RBSourceList *sourcelist = NULL;
+ RBSource *source = NULL;
+
+ g_object_get (G_OBJECT (plugin->shell),
+ "selected-source", &source,
+ NULL);
+ if ((source == NULL) || !RB_IS_MTP_SOURCE (source)) {
+ g_warning ("got MTPSourceEject action for non-mtp source");
+ if (source != NULL)
+ g_object_unref (source);
+ return;
+ }
+
+ g_object_get (plugin->shell, "sourcelist", &sourcelist, NULL);
+
+ rb_source_delete_thyself (source);
+
+ g_object_unref (sourcelist);
+ g_object_unref (source);
+}
+
+static void
+rb_mtp_plugin_rename (GtkAction *action, RBMtpPlugin *plugin)
+{
+ RBSourceList *sourcelist = NULL;
+ RBSource *source = NULL;
+
+ g_object_get (G_OBJECT (plugin->shell),
+ "selected-source", &source,
+ NULL);
+ if ((source == NULL) || !RB_IS_MTP_SOURCE (source)) {
+ g_warning ("got MTPSourceEject action for non-mtp source");
+ if (source != NULL)
+ g_object_unref (source);
+ return;
+ }
+
+ g_object_get (plugin->shell, "sourcelist", &sourcelist, NULL);
+
+ rb_sourcelist_edit_source_name (sourcelist, source);
+
+ g_object_unref (sourcelist);
+ g_object_unref (source);
+}
+
+
+#if defined(HAVE_GUDEV)
+static void
source_deleted_cb (RBMtpSource *source, RBMtpPlugin *plugin)
{
plugin->mtp_sources = g_list_remove (plugin->mtp_sources, source);
}
-#if defined(HAVE_GUDEV)
static RBSource *
create_source_device_cb (RBRemovableMediaManager *rmm, GObject *device, RBMtpPlugin *plugin)
{
@@ -339,25 +374,16 @@ create_source_device_cb (RBRemovableMediaManager *rmm, GObject *device, RBMtpPlu
/* see what devices libmtp can find */
if (LIBMTP_Detect_Raw_Devices (&raw_devices, &num_raw_devices) == 0) {
for (i = 0; i < num_raw_devices; i++) {
- LIBMTP_mtpdevice_t *device;
RBSource *source;
rb_debug ("detected mtp device: device number %d", raw_devices[i].devnum);
-
- /* check bus number/device location somehow */
if (devnum != raw_devices[i].devnum) {
rb_debug ("device number mismatches: %d vs %d", devnum, raw_devices[i].devnum);
continue;
}
- device = LIBMTP_Open_Raw_Device (&raw_devices[i]);
- if (device == NULL) {
- rb_debug ("unable to open device. weird.");
- break;
- }
-
rb_debug ("device matched, creating a source");
- source = rb_mtp_source_new (plugin->shell, device);
+ source = rb_mtp_source_new (plugin->shell, &raw_devices[i]);
plugin->mtp_sources = g_list_prepend (plugin->mtp_sources, source);
g_signal_connect_object (G_OBJECT (source),
"deleted", G_CALLBACK (source_deleted_cb),
@@ -372,118 +398,60 @@ create_source_device_cb (RBRemovableMediaManager *rmm, GObject *device, RBMtpPlu
#else
-static RBSource *
-create_source_cb (RBMtpPlugin *plugin, LIBMTP_mtpdevice_t *device, const char *udi)
+static void
+source_deleted_cb (RBMtpSource *source, RBMtpPlugin *plugin)
{
- RBSource *source;
-
- source = RB_SOURCE (rb_mtp_source_new (plugin->shell, device, udi));
-
- rb_shell_append_source (plugin->shell, source, NULL);
- plugin->mtp_sources = g_list_prepend (plugin->mtp_sources, source);
-
- g_signal_connect_object (G_OBJECT (source),
- "deleted", G_CALLBACK (source_deleted_cb),
- plugin, 0);
-
- return source;
+ plugin->mtp_sources = g_list_remove (plugin->mtp_sources, source);
}
-#endif
-
-
static void
-rb_mtp_plugin_eject (GtkAction *action, RBMtpPlugin *plugin)
+rb_mtp_plugin_maybe_add_source (RBMtpPlugin *plugin, const char *udi, LIBMTP_raw_device_t *raw_devices, int num_raw_devices)
{
- RBSourceList *sourcelist = NULL;
- RBSource *source = NULL;
+ int i;
+ int device_num = 0;
+ DBusError error;
- g_object_get (G_OBJECT (plugin->shell),
- "selected-source", &source,
- NULL);
- if ((source == NULL) || !RB_IS_MTP_SOURCE (source)) {
- g_warning ("got MTPSourceEject action for non-mtp source");
- if (source != NULL)
- g_object_unref (source);
+ rb_debug ("checking if UDI %s matches an MTP device", udi);
+
+ /* get device number */
+ dbus_error_init (&error);
+ device_num = libhal_device_get_property_int (plugin->hal_context, udi, "usb.linux.device_number", &error);
+ if (dbus_error_is_set (&error)) {
+ rb_debug ("unable to get USB device number: %s", error.message);
+ dbus_error_free (&error);
return;
}
- g_object_get (plugin->shell, "sourcelist", &sourcelist, NULL);
+ rb_debug ("USB device number: %d", device_num);
- rb_source_delete_thyself (source);
-
- g_object_unref (sourcelist);
- g_object_unref (source);
-}
+ for (i = 0; i < num_raw_devices; i++) {
+ rb_debug ("detected MTP device: device number %d (bus location %u)", raw_devices[i].devnum, raw_devices[i].bus_location);
+ if (raw_devices[i].devnum == device_num) {
+ RBSource *source;
+ rb_debug ("device matched, creating a source");
+ source = RB_SOURCE (rb_mtp_source_new (plugin->shell, &raw_devices[i], udi));
-static void
-rb_mtp_plugin_rename (GtkAction *action, RBMtpPlugin *plugin)
-{
- RBSourceList *sourcelist = NULL;
- RBSource *source = NULL;
+ rb_shell_append_source (plugin->shell, source, NULL);
+ plugin->mtp_sources = g_list_prepend (plugin->mtp_sources, source);
- g_object_get (G_OBJECT (plugin->shell),
- "selected-source", &source,
- NULL);
- if ((source == NULL) || !RB_IS_MTP_SOURCE (source)) {
- g_warning ("got MTPSourceEject action for non-mtp source");
- if (source != NULL)
- g_object_unref (source);
- return;
+ g_signal_connect_object (G_OBJECT (source),
+ "deleted", G_CALLBACK (source_deleted_cb),
+ plugin, 0);
+ }
}
-
- g_object_get (plugin->shell, "sourcelist", &sourcelist, NULL);
-
- rb_sourcelist_edit_source_name (sourcelist, source);
-
- g_object_unref (sourcelist);
- g_object_unref (source);
}
-#if !defined(HAVE_GUDEV)
-
static void
rb_mtp_plugin_device_added (LibHalContext *context, const char *udi)
{
RBMtpPlugin *plugin = (RBMtpPlugin *) libhal_ctx_get_user_data (context);
- LIBMTP_device_entry_t *entries;
- int numentries;
- int vendor_id;
- int product_id;
- int ret;
-
- if (g_list_length (plugin->mtp_sources) > 0) {
- rb_debug ("plugin only supports one device at the time right now.");
- return;
- }
+ LIBMTP_raw_device_t *mtp_devices;
+ int num_mtp_devices;
- vendor_id = libhal_device_get_property_int (context, udi, "usb.vendor_id", NULL);
- product_id = libhal_device_get_property_int (context, udi, "usb.product_id", NULL);
-
- ret = LIBMTP_Get_Supported_Devices_List (&entries, &numentries);
- if (ret == 0) {
- int i, p;
-
- for (i = 0; i < numentries; i++) {
- if ((entries[i].vendor_id==vendor_id) && (entries[i].product_id == product_id)) {
- /*
- * FIXME:
- *
- * It usualy takes a while for the device to set itself up.
- * Solving that by trying 10 times with some sleep in between.
- * There is probably a better solution, but this works.
- */
- rb_debug ("adding device source");
- for (p = 0; p < 10; p++) {
- LIBMTP_mtpdevice_t *device = LIBMTP_Get_First_Device ();
- if (device != NULL) {
- create_source_cb (plugin, device, udi);
- break;
- }
- usleep (200000);
- }
- }
- }
+ LIBMTP_Detect_Raw_Devices (&mtp_devices, &num_mtp_devices);
+ if (mtp_devices != NULL) {
+ rb_mtp_plugin_maybe_add_source (plugin, udi, mtp_devices, num_mtp_devices);
+ free (mtp_devices);
}
}
diff --git a/plugins/mtpdevice/rb-mtp-source.c b/plugins/mtpdevice/rb-mtp-source.c
index e41872f..a51fbe5 100644
--- a/plugins/mtpdevice/rb-mtp-source.c
+++ b/plugins/mtpdevice/rb-mtp-source.c
@@ -50,6 +50,7 @@
#include "rb-encoder.h"
#include "rb-mtp-source.h"
+#include "rb-mtp-thread.h"
#define CONF_STATE_PANED_POSITION CONF_PREFIX "/state/mtp/paned_position"
#define CONF_STATE_SHOW_BROWSER CONF_PREFIX "/state/mtp/show_browser"
@@ -71,8 +72,6 @@ static void rb_mtp_source_get_property (GObject *object,
static char *impl_get_browser_key (RBSource *source);
static char *impl_get_paned_key (RBBrowserSource *source);
-static void rb_mtp_source_load_tracks (RBMtpSource*);
-
static void impl_delete (RBSource *asource);
static gboolean impl_show_popup (RBSource *source);
static GList* impl_get_ui_actions (RBSource *source);
@@ -82,11 +81,17 @@ static gboolean impl_track_added (RBRemovableMediaSource *source,
RhythmDBEntry *entry,
const char *dest,
const char *mimetype);
+static gboolean impl_track_add_error (RBRemovableMediaSource *source,
+ RhythmDBEntry *entry,
+ const char *dest,
+ GError *error);
static char* impl_build_dest_uri (RBRemovableMediaSource *source,
RhythmDBEntry *entry,
const char *mimetype,
const char *extension);
+static void mtp_device_open_cb (LIBMTP_mtpdevice_t *device, RBMtpSource *source);
+static void mtp_tracklist_cb (LIBMTP_track_t *tracks, RBMtpSource *source);
static RhythmDB * get_db_for_source (RBMtpSource *source);
static void artwork_notify_cb (RhythmDB *db,
RhythmDBEntry *entry,
@@ -94,8 +99,6 @@ static void artwork_notify_cb (RhythmDB *db,
const GValue *metadata,
RBMtpSource *source);
-static void add_track_to_album (RBMtpSource *source, const char *album_name, LIBMTP_track_t *track);
-
static void prepare_player_source_cb (RBPlayer *player,
const char *stream_uri,
GstElement *src,
@@ -104,13 +107,18 @@ static void prepare_encoder_source_cb (RBEncoderFactory *factory,
const char *stream_uri,
GObject *src,
RBMtpSource *source);
+static void prepare_encoder_sink_cb (RBEncoderFactory *factory,
+ const char *stream_uri,
+ GObject *sink,
+ RBMtpSource *source);
typedef struct
{
- LIBMTP_mtpdevice_t *device;
+ RBMtpThread *device_thread;
+ LIBMTP_raw_device_t *raw_device;
GHashTable *entry_map;
- GHashTable *album_map;
GHashTable *artwork_request_map;
+ GHashTable *track_transfer_map;
#if !defined(HAVE_GUDEV)
char *udi;
#endif
@@ -118,7 +126,6 @@ typedef struct
GList *mediatypes;
gboolean album_art_supported;
- guint load_songs_idle_id;
} RBMtpSourcePrivate;
RB_PLUGIN_DEFINE_TYPE(RBMtpSource,
@@ -130,30 +137,11 @@ RB_PLUGIN_DEFINE_TYPE(RBMtpSource,
enum
{
PROP_0,
- PROP_LIBMTP_DEVICE,
+ PROP_RAW_DEVICE,
PROP_UDI,
};
static void
-report_libmtp_errors (LIBMTP_mtpdevice_t *device, gboolean use_dialog)
-{
- LIBMTP_error_t *stack;
-
- for (stack = LIBMTP_Get_Errorstack (device); stack != NULL; stack = stack->next) {
- if (use_dialog) {
- rb_error_dialog (NULL, _("Media player device error"), "%s", stack->error_text);
-
- /* only display one dialog box per error */
- use_dialog = FALSE;
- } else {
- g_warning ("libmtp error: %s", stack->error_text);
- }
- }
-
- LIBMTP_Clear_Errorstack (device);
-}
-
-static void
rb_mtp_source_class_init (RBMtpSourceClass *klass)
{
GObjectClass *object_class = G_OBJECT_CLASS (klass);
@@ -184,15 +172,16 @@ rb_mtp_source_class_init (RBMtpSourceClass *klass)
browser_source_class->impl_get_paned_key = impl_get_paned_key;
rms_class->impl_track_added = impl_track_added;
+ rms_class->impl_track_add_error = impl_track_add_error;
rms_class->impl_build_dest_uri = impl_build_dest_uri;
rms_class->impl_get_mime_types = impl_get_mime_types;
rms_class->impl_should_paste = rb_removable_media_source_should_paste_no_duplicate;
g_object_class_install_property (object_class,
- PROP_LIBMTP_DEVICE,
- g_param_spec_pointer ("libmtp-device",
- "libmtp-device",
- "libmtp device",
+ PROP_RAW_DEVICE,
+ g_param_spec_pointer ("raw-device",
+ "raw-device",
+ "libmtp raw device",
G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
#if !defined(HAVE_GUDEV)
g_object_class_install_property (object_class,
@@ -216,9 +205,7 @@ rb_mtp_source_name_changed_cb (RBMtpSource *source,
char *name = NULL;
g_object_get (source, "name", &name, NULL);
- if (LIBMTP_Set_Friendlyname (priv->device, name) != 0) {
- report_libmtp_errors (priv->device, TRUE);
- }
+ rb_mtp_thread_set_device_name (priv->device_thread, name);
g_free (name);
}
@@ -231,11 +218,9 @@ rb_mtp_source_init (RBMtpSource *source)
g_direct_equal,
NULL,
(GDestroyNotify) LIBMTP_destroy_track_t);
- priv->album_map = g_hash_table_new_full (g_str_hash,
- g_str_equal,
- NULL,
- (GDestroyNotify) LIBMTP_destroy_album_t);
priv->artwork_request_map = g_hash_table_new (g_direct_hash, g_direct_equal);
+
+ priv->track_transfer_map = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL);
}
static GObject *
@@ -251,14 +236,16 @@ rb_mtp_source_constructor (GType type, guint n_construct_properties,
GtkIconTheme *theme;
GdkPixbuf *pixbuf;
gint size;
- guint16 *types = NULL;
- guint16 num_types= 0;
source = RB_MTP_SOURCE (G_OBJECT_CLASS (rb_mtp_source_parent_class)->
constructor (type, n_construct_properties, construct_properties));
priv = MTP_SOURCE_GET_PRIVATE (source);
+ /* start the device thread */
+ priv->device_thread = rb_mtp_thread_new ();
+ rb_mtp_thread_open_device (priv->device_thread, priv->raw_device, (RBMtpOpenCallback)mtp_device_open_cb, g_object_ref (source), g_object_unref);
+
tracks = rb_source_get_entry_view (RB_SOURCE (source));
rb_entry_view_append_column (tracks, RB_ENTRY_VIEW_COL_RATING, FALSE);
rb_entry_view_append_column (tracks, RB_ENTRY_VIEW_COL_LAST_PLAYED, FALSE);
@@ -280,6 +267,10 @@ rb_mtp_source_constructor (GType type, guint n_construct_properties,
"prepare-source",
G_CALLBACK (prepare_encoder_source_cb),
source, 0);
+ g_signal_connect_object (rb_encoder_factory_get (),
+ "prepare-sink",
+ G_CALLBACK (prepare_encoder_sink_cb),
+ source, 0);
/* icon */
theme = gtk_icon_theme_get_default ();
@@ -289,82 +280,6 @@ rb_mtp_source_constructor (GType type, guint n_construct_properties,
rb_source_set_pixbuf (RB_SOURCE (source), pixbuf);
g_object_unref (pixbuf);
- g_signal_connect (G_OBJECT (source), "notify::name",
- (GCallback)rb_mtp_source_name_changed_cb, NULL);
-
- /* figure out supported file types */
- if (LIBMTP_Get_Supported_Filetypes(priv->device, &types, &num_types) == 0) {
- int i;
- gboolean has_mp3 = FALSE;
- for (i = 0; i < num_types; i++) {
- const char *mediatype;
-
- if (i <= LIBMTP_FILETYPE_UNKNOWN) {
- priv->supported_types[types[i]] = 1;
- }
-
- /* this has to work with the remapping done in
- * rb-removable-media-source.c:impl_paste.
- */
- switch (types[i]) {
- case LIBMTP_FILETYPE_WAV:
- mediatype = "audio/x-wav";
- break;
- case LIBMTP_FILETYPE_MP3:
- /* special handling for mp3: always put it at the front of the list
- * if it's supported.
- */
- has_mp3 = TRUE;
- mediatype = NULL;
- break;
- case LIBMTP_FILETYPE_WMA:
- mediatype = "audio/x-ms-wma";
- break;
- case LIBMTP_FILETYPE_OGG:
- mediatype = "application/ogg";
- break;
- case LIBMTP_FILETYPE_MP4:
- case LIBMTP_FILETYPE_M4A:
- case LIBMTP_FILETYPE_AAC:
- mediatype = "audio/aac";
- break;
- case LIBMTP_FILETYPE_WMV:
- mediatype = "audio/x-ms-wmv";
- break;
- case LIBMTP_FILETYPE_ASF:
- mediatype = "video/x-ms-asf";
- break;
- case LIBMTP_FILETYPE_FLAC:
- mediatype = "audio/flac";
- break;
-
- case LIBMTP_FILETYPE_JPEG:
- rb_debug ("JPEG (album art) supported");
- mediatype = NULL;
- priv->album_art_supported = TRUE;
- break;
-
- default:
- rb_debug ("unknown libmtp filetype %s supported", LIBMTP_Get_Filetype_Description (types[i]));
- mediatype = NULL;
- break;
- }
-
- if (mediatype != NULL) {
- rb_debug ("media type %s supported", mediatype);
- priv->mediatypes = g_list_prepend (priv->mediatypes,
- g_strdup (mediatype));
- }
- }
-
- if (has_mp3) {
- rb_debug ("audio/mpeg supported");
- priv->mediatypes = g_list_prepend (priv->mediatypes, g_strdup ("audio/mpeg"));
- }
- } else {
- report_libmtp_errors (priv->device, FALSE);
- }
-
if (priv->album_art_supported) {
RhythmDB *db;
@@ -374,8 +289,6 @@ rb_mtp_source_constructor (GType type, guint n_construct_properties,
g_object_unref (db);
}
- rb_mtp_source_load_tracks (source);
-
return G_OBJECT (source);
}
@@ -388,8 +301,8 @@ rb_mtp_source_set_property (GObject *object,
RBMtpSourcePrivate *priv = MTP_SOURCE_GET_PRIVATE (object);
switch (prop_id) {
- case PROP_LIBMTP_DEVICE:
- priv->device = g_value_get_pointer (value);
+ case PROP_RAW_DEVICE:
+ priv->raw_device = g_value_get_pointer (value);
break;
#if !defined(HAVE_GUDEV)
case PROP_UDI:
@@ -411,8 +324,8 @@ rb_mtp_source_get_property (GObject *object,
RBMtpSourcePrivate *priv = MTP_SOURCE_GET_PRIVATE (object);
switch (prop_id) {
- case PROP_LIBMTP_DEVICE:
- g_value_set_pointer (value, priv->device);
+ case PROP_RAW_DEVICE:
+ g_value_set_pointer (value, priv->raw_device);
break;
#if !defined(HAVE_GUDEV)
case PROP_UDI:
@@ -433,6 +346,11 @@ rb_mtp_source_dispose (GObject *object)
RhythmDBEntryType entry_type;
RhythmDB *db;
+ if (priv->device_thread != NULL) {
+ g_object_unref (priv->device_thread);
+ priv->device_thread = NULL;
+ }
+
db = get_db_for_source (source);
g_object_get (G_OBJECT (source), "entry-type", &entry_type, NULL);
@@ -442,11 +360,6 @@ rb_mtp_source_dispose (GObject *object)
rhythmdb_commit (db);
g_object_unref (db);
- if (priv->load_songs_idle_id != 0) {
- g_source_remove (priv->load_songs_idle_id);
- priv->load_songs_idle_id = 0;
- }
-
G_OBJECT_CLASS (rb_mtp_source_parent_class)->dispose (object);
}
@@ -456,15 +369,13 @@ rb_mtp_source_finalize (GObject *object)
RBMtpSourcePrivate *priv = MTP_SOURCE_GET_PRIVATE (object);
g_hash_table_destroy (priv->entry_map);
- g_hash_table_destroy (priv->album_map);
g_hash_table_destroy (priv->artwork_request_map);
+ g_hash_table_destroy (priv->track_transfer_map); /* probably need to destroy the tracks too.. */
#if !defined(HAVE_GUDEV)
g_free (priv->udi);
#endif
- LIBMTP_Release_Device (priv->device);
-
G_OBJECT_CLASS (rb_mtp_source_parent_class)->finalize (object);
}
@@ -483,11 +394,11 @@ impl_get_paned_key (RBBrowserSource *source)
#if defined(HAVE_GUDEV)
RBSource *
rb_mtp_source_new (RBShell *shell,
- LIBMTP_mtpdevice_t *device)
+ LIBMTP_raw_device_t *device)
#else
RBSource *
rb_mtp_source_new (RBShell *shell,
- LIBMTP_mtpdevice_t *device,
+ LIBMTP_raw_device_t *device,
const char *udi)
#endif
{
@@ -497,7 +408,7 @@ rb_mtp_source_new (RBShell *shell,
char *name = NULL;
g_object_get (shell, "db", &db, NULL);
- name = g_strdup_printf ("MTP-%s", LIBMTP_Get_Serialnumber (device));
+ name = g_strdup_printf ("MTP-%u-%d", device->bus_location, device->devnum);
entry_type = rhythmdb_entry_register_type (db, name);
entry_type->save_to_disk = FALSE;
@@ -512,7 +423,7 @@ rb_mtp_source_new (RBShell *shell,
"visibility", TRUE,
"volume", NULL,
"source-group", RB_SOURCE_GROUP_DEVICES,
- "libmtp-device", device,
+ "raw-device", device,
#if !defined(HAVE_GUDEV)
"udi", udi,
#endif
@@ -540,7 +451,7 @@ entry_set_string_prop (RhythmDB *db,
g_value_unset (&value);
}
-static void
+static RhythmDBEntry *
add_mtp_track_to_db (RBMtpSource *source,
RhythmDB *db,
LIBMTP_track_t *track)
@@ -555,7 +466,7 @@ add_mtp_track_to_db (RBMtpSource *source,
rb_debug ("ignoring non-audio item %d (filetype %s)",
track->item_id,
LIBMTP_Get_Filetype_Description (track->filetype));
- return;
+ return NULL;
}
/* Set URI */
@@ -568,7 +479,7 @@ add_mtp_track_to_db (RBMtpSource *source,
if (entry == NULL) {
rb_debug ("cannot create entry %i", track->item_id);
g_object_unref (G_OBJECT (db));
- return;
+ return NULL;
}
/* Set track number */
@@ -635,109 +546,166 @@ add_mtp_track_to_db (RBMtpSource *source,
g_hash_table_insert (priv->entry_map, entry, track);
rhythmdb_commit (RHYTHMDB (db));
+
+ return entry;
}
+typedef struct {
+ RBMtpSource *source;
+ char *name;
+ guint16 *types;
+ guint16 num_types;
+} DeviceOpenedData;
+
static gboolean
-load_mtp_db_idle_cb (RBMtpSource* source)
+device_opened_idle (DeviceOpenedData *data)
{
- RhythmDB *db = NULL;
- RBMtpSourcePrivate *priv = MTP_SOURCE_GET_PRIVATE (source);
- LIBMTP_track_t *tracks = NULL;
- LIBMTP_album_t *albums;
- gboolean device_forgets_albums = TRUE;
-
- db = get_db_for_source (source);
+ RBMtpSourcePrivate *priv = MTP_SOURCE_GET_PRIVATE (data->source);
+ int i;
+ gboolean has_mp3 = FALSE;
- g_assert (db != NULL);
+ if (data->name != NULL) {
+ g_object_set (data->source, "name", data->name, NULL);
+ }
- albums = LIBMTP_Get_Album_List (priv->device);
- report_libmtp_errors (priv->device, FALSE);
- if (albums != NULL) {
- LIBMTP_album_t *album;
+ /* when the source name changes after this, try to update the device name */
+ g_signal_connect (G_OBJECT (data->source), "notify::name",
+ (GCallback)rb_mtp_source_name_changed_cb, NULL);
- for (album = albums; album != NULL; album = album->next) {
- if (album->name == NULL)
- continue;
+ for (i = 0; i < data->num_types; i++) {
+ const char *mediatype;
- rb_debug ("album: %s, %d tracks", album->name, album->no_tracks);
- g_hash_table_insert (priv->album_map, album->name, album);
- if (album->no_tracks != 0) {
- device_forgets_albums = FALSE;
- }
+ if (i <= LIBMTP_FILETYPE_UNKNOWN) {
+ priv->supported_types[data->types[i]] = 1;
}
- if (device_forgets_albums) {
- rb_debug ("stupid mtp device detected. will rebuild all albums.");
- }
- } else {
- rb_debug ("No albums");
- device_forgets_albums = FALSE;
- }
+ /* this has to work with the remapping done in
+ * rb-removable-media-source.c:impl_paste.
+ */
+ switch (data->types[i]) {
+ case LIBMTP_FILETYPE_WAV:
+ mediatype = "audio/x-wav";
+ break;
+ case LIBMTP_FILETYPE_MP3:
+ /* special handling for mp3: always put it at the front of the list
+ * if it's supported.
+ */
+ has_mp3 = TRUE;
+ mediatype = NULL;
+ break;
+ case LIBMTP_FILETYPE_WMA:
+ mediatype = "audio/x-ms-wma";
+ break;
+ case LIBMTP_FILETYPE_OGG:
+ mediatype = "application/ogg";
+ break;
+ case LIBMTP_FILETYPE_MP4:
+ case LIBMTP_FILETYPE_M4A:
+ case LIBMTP_FILETYPE_AAC:
+ mediatype = "audio/aac";
+ break;
+ case LIBMTP_FILETYPE_WMV:
+ mediatype = "audio/x-ms-wmv";
+ break;
+ case LIBMTP_FILETYPE_ASF:
+ mediatype = "video/x-ms-asf";
+ break;
+ case LIBMTP_FILETYPE_FLAC:
+ mediatype = "audio/flac";
+ break;
- tracks = LIBMTP_Get_Tracklisting_With_Callback (priv->device, NULL, NULL);
- report_libmtp_errors (priv->device, FALSE);
- if (tracks != NULL) {
- LIBMTP_track_t *track;
- for (track = tracks; track != NULL; track = track->next) {
- add_mtp_track_to_db (source, db, track);
+ case LIBMTP_FILETYPE_JPEG:
+ rb_debug ("JPEG (album art) supported");
+ mediatype = NULL;
+ priv->album_art_supported = TRUE;
+ break;
- if (device_forgets_albums && track->album != NULL) {
- add_track_to_album (source, track->album, track);
- }
+ default:
+ rb_debug ("unknown libmtp filetype %s supported", LIBMTP_Get_Filetype_Description (data->types[i]));
+ mediatype = NULL;
+ break;
}
- } else {
- rb_debug ("No tracks");
- }
- /* for stupid devices, remove any albums left with no tracks */
- if (device_forgets_albums) {
- GHashTableIter iter;
- gpointer value;
- LIBMTP_album_t *album;
-
- g_hash_table_iter_init (&iter, priv->album_map);
- while (g_hash_table_iter_next (&iter, NULL, &value)) {
- int ret;
-
- album = value;
- if (album->no_tracks == 0) {
- rb_debug ("pruning empty album \"%s\"", album->name);
- ret = LIBMTP_Delete_Object (priv->device, album->album_id);
- if (ret != 0) {
- report_libmtp_errors (priv->device, FALSE);
- }
- g_hash_table_iter_remove (&iter);
- }
+ if (mediatype != NULL) {
+ rb_debug ("media type %s supported", mediatype);
+ priv->mediatypes = g_list_prepend (priv->mediatypes,
+ g_strdup (mediatype));
}
}
+ if (has_mp3) {
+ rb_debug ("audio/mpeg supported");
+ priv->mediatypes = g_list_prepend (priv->mediatypes, g_strdup ("audio/mpeg"));
+ }
+
+ g_object_unref (data->source);
+ free (data->types);
+ g_free (data->name);
+ g_free (data);
- g_object_unref (G_OBJECT (db));
return FALSE;
}
+static gboolean
+device_open_failed_idle (RBMtpSource *source)
+{
+ rb_source_delete_thyself (RB_SOURCE (source));
+ g_object_unref (source);
+ return FALSE;
+}
+
+/* this callback runs on the device handling thread, so it can call libmtp directly */
static void
-rb_mtp_source_load_tracks (RBMtpSource *source)
+mtp_device_open_cb (LIBMTP_mtpdevice_t *device, RBMtpSource *source)
{
RBMtpSourcePrivate *priv = MTP_SOURCE_GET_PRIVATE (source);
- char *name = NULL;
+ DeviceOpenedData *data;
- name = LIBMTP_Get_Friendlyname (priv->device);
- /* ignore some particular broken device names */
- if (name == NULL || strcmp (name, "?????") == 0) {
- g_free (name);
- name = LIBMTP_Get_Modelname (priv->device);
+ if (device == NULL) {
+ rb_mtp_thread_report_errors (priv->device_thread, TRUE);
+
+ /* can't delete the source on this thread, so move it to the main thread */
+ g_idle_add ((GSourceFunc) device_open_failed_idle, g_object_ref (source));
+ return;
+ }
+
+ /* set the source name to match the device, ignoring some
+ * particular broken device names.
+ */
+ data = g_new0 (DeviceOpenedData, 1);
+ data->source = g_object_ref (source);
+ data->name = LIBMTP_Get_Friendlyname (device);
+ if (data->name == NULL || strcmp (data->name, "?????") == 0) {
+ g_free (data->name);
+ data->name = LIBMTP_Get_Modelname (device);
}
- if (name == NULL) {
- name = g_strdup (_("Digital Audio Player"));
+ if (data->name == NULL) {
+ data->name = g_strdup (_("Digital Audio Player"));
}
- g_object_set (RB_SOURCE (source),
- "name", name,
- NULL);
+ /* figure out the set of formats supported by the device */
+ if (LIBMTP_Get_Supported_Filetypes (device, &data->types, &data->num_types) != 0) {
+ rb_mtp_thread_report_errors (priv->device_thread, FALSE);
+ }
- priv->load_songs_idle_id = g_idle_add ((GSourceFunc)load_mtp_db_idle_cb, source);
- g_free (name);
+ g_idle_add ((GSourceFunc) device_opened_idle, data);
+
+ /* now get the track list */
+ rb_mtp_thread_get_track_list (priv->device_thread, (RBMtpTrackListCallback) mtp_tracklist_cb, g_object_ref (source), g_object_unref);
+}
+
+static void
+mtp_tracklist_cb (LIBMTP_track_t *tracks, RBMtpSource *source)
+{
+ RhythmDB *db = NULL;
+ LIBMTP_track_t *track;
+
+ /* add tracks to database */
+ db = get_db_for_source (source);
+ for (track = tracks; track != NULL; track = track->next) {
+ add_mtp_track_to_db (source, db, track);
+ }
+ g_object_unref (db);
}
static char *
@@ -782,92 +750,6 @@ mimetype_to_filetype (RBMtpSource *source, const char *mimetype)
}
static void
-add_track_to_album (RBMtpSource *source, const char *album_name, LIBMTP_track_t *track)
-{
- RBMtpSourcePrivate *priv = MTP_SOURCE_GET_PRIVATE (source);
- LIBMTP_album_t *album;
-
- album = g_hash_table_lookup (priv->album_map, album_name);
- if (album != NULL) {
- /* add track to album */
-
- album->tracks = realloc (album->tracks, sizeof(uint32_t) * (album->no_tracks+1));
- album->tracks[album->no_tracks] = track->item_id;
- album->no_tracks++;
- rb_debug ("adding track ID %d to album ID %d; now %d tracks",
- track->item_id,
- album->album_id,
- album->no_tracks);
-
- if (LIBMTP_Update_Album (priv->device, album) != 0) {
- rb_debug ("LIBMTP_Update_Album failed..");
- report_libmtp_errors (priv->device, FALSE);
- }
- } else {
- /* add new album */
- album = LIBMTP_new_album_t ();
- album->name = strdup (album_name);
- album->no_tracks = 1;
- album->tracks = malloc (sizeof(uint32_t));
- album->tracks[0] = track->item_id;
- album->storage_id = track->storage_id;
-
- /* fill in artist and genre? */
-
- rb_debug ("creating new album (%s) for track ID %d", album->name, track->item_id);
-
- if (LIBMTP_Create_New_Album (priv->device, album) != 0) {
- LIBMTP_destroy_album_t (album);
- rb_debug ("LIBMTP_Create_New_Album failed..");
- report_libmtp_errors (priv->device, FALSE);
- } else {
- g_hash_table_insert (priv->album_map, album->name, album);
- }
- }
-}
-
-static void
-remove_track_from_album (RBMtpSource *source, const char *album_name, LIBMTP_track_t *track)
-{
- RBMtpSourcePrivate *priv = MTP_SOURCE_GET_PRIVATE (source);
- LIBMTP_album_t *album;
- int i;
-
- album = g_hash_table_lookup (priv->album_map, album_name);
- if (album == NULL) {
- rb_debug ("Couldn't find an album for %s", album_name);
- return;
- }
-
- for (i = 0; i < album->no_tracks; i++) {
- if (album->tracks[i] == track->item_id) {
- break;
- }
- }
-
- if (i == album->no_tracks) {
- rb_debug ("Couldn't find track %d in album %d", track->item_id, album->album_id);
- return;
- }
-
- memmove (album->tracks + i, album->tracks + i + 1, album->no_tracks - (i+1));
- album->no_tracks--;
-
- if (album->no_tracks == 0) {
- rb_debug ("deleting empty album %d", album->album_id);
- if (LIBMTP_Delete_Object (priv->device, album->album_id) != 0) {
- report_libmtp_errors (priv->device, FALSE);
- }
- g_hash_table_remove (priv->album_map, album_name);
- } else {
- rb_debug ("updating album %d: %d tracks remaining", album->album_id, album->no_tracks);
- if (LIBMTP_Update_Album (priv->device, album) != 0) {
- report_libmtp_errors (priv->device, FALSE);
- }
- }
-}
-
-static void
impl_delete (RBSource *source)
{
RBMtpSourcePrivate *priv = MTP_SOURCE_GET_PRIVATE (source);
@@ -875,7 +757,6 @@ impl_delete (RBSource *source)
GList *tem;
RBEntryView *tracks;
RhythmDB *db;
- int ret;
db = get_db_for_source (RB_MTP_SOURCE (source));
@@ -895,17 +776,11 @@ impl_delete (RBSource *source)
continue;
}
- ret = LIBMTP_Delete_Object (priv->device, track->item_id);
- if (ret != 0) {
- rb_debug ("Delete track %d failed", track->item_id);
- report_libmtp_errors (priv->device, TRUE);
- continue;
- }
-
album_name = rhythmdb_entry_get_string (entry, RHYTHMDB_PROP_ALBUM);
if (strcmp (album_name, _("Unknown")) != 0) {
- remove_track_from_album (RB_MTP_SOURCE (source), album_name, track);
+ rb_mtp_thread_remove_from_album (priv->device_thread, track, album_name);
}
+ rb_mtp_thread_delete_track (priv->device_thread, track);
g_hash_table_remove (priv->entry_map, entry);
rhythmdb_entry_delete (db, entry);
@@ -946,55 +821,41 @@ get_db_for_source (RBMtpSource *source)
return db;
}
-static LIBMTP_track_t *
-transfer_track (RBMtpSource *source,
- LIBMTP_mtpdevice_t *device,
- RhythmDBEntry *entry,
- const char *filename,
- const char *mimetype)
-{
- LIBMTP_track_t *trackmeta = LIBMTP_new_track_t ();
- GDate d;
- int ret;
-
- trackmeta->title = rhythmdb_entry_dup_string (entry, RHYTHMDB_PROP_TITLE);
- trackmeta->album = rhythmdb_entry_dup_string (entry, RHYTHMDB_PROP_ALBUM);
- trackmeta->artist = rhythmdb_entry_dup_string (entry, RHYTHMDB_PROP_ARTIST);
- trackmeta->genre = rhythmdb_entry_dup_string (entry, RHYTHMDB_PROP_GENRE);
- trackmeta->filename = g_path_get_basename (filename);
- rb_sanitize_path_for_msdos_filesystem (trackmeta->filename);
+typedef struct {
+ RBMtpSource *source;
+ RhythmDBEntry *entry;
+} RequestAlbumArtData;
- if (rhythmdb_entry_get_ulong (entry, RHYTHMDB_PROP_DATE) > 0) { /* Entries without a date returns 0, g_date_set_julian don't accept that */
- g_date_set_julian (&d, rhythmdb_entry_get_ulong (entry, RHYTHMDB_PROP_DATE));
- trackmeta->date = gdate_to_char (&d);
- }
- trackmeta->tracknumber = rhythmdb_entry_get_ulong (entry, RHYTHMDB_PROP_TRACK_NUMBER);
- trackmeta->duration = rhythmdb_entry_get_ulong (entry, RHYTHMDB_PROP_DURATION) * 1000;
- trackmeta->rating = rhythmdb_entry_get_double (entry, RHYTHMDB_PROP_RATING) * 20;
- trackmeta->usecount = rhythmdb_entry_get_ulong (entry, RHYTHMDB_PROP_PLAY_COUNT);
- trackmeta->filesize = rhythmdb_entry_get_uint64 (entry, RHYTHMDB_PROP_FILE_SIZE);
- if (mimetype == NULL) {
- mimetype = rhythmdb_entry_get_string (entry, RHYTHMDB_PROP_MIMETYPE);
- }
- trackmeta->filetype = mimetype_to_filetype (source, mimetype);
- rb_debug ("using libmtp filetype %d (%s) for source media type %s",
- trackmeta->filetype,
- LIBMTP_Get_Filetype_Description (trackmeta->filetype),
- mimetype);
+static gboolean
+request_album_art_idle (RequestAlbumArtData *data)
+{
+ RBMtpSourcePrivate *priv = MTP_SOURCE_GET_PRIVATE (data->source);
+ const char *album;
- ret = LIBMTP_Send_Track_From_File (device, filename, trackmeta, NULL, NULL);
- rb_debug ("LIBMTP_Send_Track_From_File (%s) returned %d", filename, ret);
- if (ret != 0) {
- report_libmtp_errors (device, TRUE);
- LIBMTP_destroy_track_t (trackmeta);
- return NULL;
- }
+ /* pretty sure we don't need any extra locking here - we only touch the artwork
+ * request map on the main thread anyway.
+ */
- if (strcmp (trackmeta->album, _("Unknown")) != 0) {
- add_track_to_album (source, trackmeta->album, trackmeta);
+ album = rhythmdb_entry_get_string (data->entry, RHYTHMDB_PROP_ALBUM);
+ if (g_hash_table_lookup (priv->artwork_request_map, album) == NULL) {
+ GValue *metadata;
+ RhythmDB *db = get_db_for_source (data->source);
+
+ rb_debug ("requesting cover art image for album %s", album);
+ g_hash_table_insert (priv->artwork_request_map, (gpointer) album, GINT_TO_POINTER (1));
+ metadata = rhythmdb_entry_request_extra_metadata (db, data->entry, "rb:coverArt");
+ if (metadata) {
+ artwork_notify_cb (db, data->entry, "rb:coverArt", metadata, data->source);
+ g_value_unset (metadata);
+ g_free (metadata);
+ }
+ g_object_unref (db);
}
- return trackmeta;
+ g_object_unref (data->source);
+ rhythmdb_entry_unref (data->entry);
+ g_free (data);
+ return FALSE;
}
static GList *
@@ -1005,50 +866,123 @@ impl_get_mime_types (RBRemovableMediaSource *source)
}
static gboolean
-impl_track_added (RBRemovableMediaSource *isource,
+impl_track_added (RBRemovableMediaSource *source,
RhythmDBEntry *entry,
const char *dest,
const char *mimetype)
{
- RBMtpSource *source = RB_MTP_SOURCE (isource);
- RBMtpSourcePrivate *priv = MTP_SOURCE_GET_PRIVATE (source);
- GFile *file;
- char *path;
LIBMTP_track_t *track = NULL;
+ RBMtpSourcePrivate *priv = MTP_SOURCE_GET_PRIVATE (source);
+ RhythmDB *db;
+ RhythmDBEntry *mtp_entry;
- file = g_file_new_for_uri (dest);
- path = g_file_get_path (file);
- track = transfer_track (source, priv->device, entry, path, mimetype);
- g_free (path);
+ track = g_hash_table_lookup (priv->track_transfer_map, dest);
+ if (track == NULL) {
+ rb_debug ("track-added called, but can't find a track for dest URI %s", dest);
+ return FALSE;
+ }
+ g_hash_table_remove (priv->track_transfer_map, dest);
- g_file_delete (file, NULL, NULL);
+ db = get_db_for_source (RB_MTP_SOURCE (source));
+ /* entry_map takes ownership of the track here */
+ mtp_entry = add_mtp_track_to_db (RB_MTP_SOURCE (source), db, track);
+ g_object_unref (db);
+
+ if (strcmp (track->album, _("Unknown")) != 0) {
+ rb_mtp_thread_add_to_album (priv->device_thread, track, track->album);
+ }
+ if (priv->album_art_supported) {
+ RequestAlbumArtData *artdata;
+ artdata = g_new0 (RequestAlbumArtData, 1);
+ artdata->source = g_object_ref (source);
+ artdata->entry = rhythmdb_entry_ref (mtp_entry);
+ g_idle_add ((GSourceFunc) request_album_art_idle, artdata);
+ }
+ return FALSE;
+}
+
+static gboolean
+impl_track_add_error (RBRemovableMediaSource *source,
+ RhythmDBEntry *entry,
+ const char *dest,
+ GError *error)
+{
+ RBMtpSourcePrivate *priv = MTP_SOURCE_GET_PRIVATE (source);
+ /* we don't actually do anything with the error here, we just need to clean up the transfer map */
+ LIBMTP_track_t *track = g_hash_table_lookup (priv->track_transfer_map, dest);
if (track != NULL) {
- RhythmDB *db = get_db_for_source (source);
-
- if (priv->album_art_supported) {
- const char *album;
-
- album = rhythmdb_entry_get_string (entry, RHYTHMDB_PROP_ALBUM);
- if (g_hash_table_lookup (priv->artwork_request_map, album) == NULL) {
- GValue *metadata;
-
- rb_debug ("requesting cover art image for album %s", album);
- g_hash_table_insert (priv->artwork_request_map, (gpointer) album, GINT_TO_POINTER (1));
- metadata = rhythmdb_entry_request_extra_metadata (db, entry, "rb:coverArt");
- if (metadata) {
- artwork_notify_cb (db, entry, "rb:coverArt", metadata, source);
- g_value_unset (metadata);
- g_free (metadata);
- }
- }
- }
+ LIBMTP_destroy_track_t (track);
+ g_hash_table_remove (priv->track_transfer_map, dest);
+ } else {
+ rb_debug ("track-add-error called, but can't find a track for dest URI %s", dest);
+ }
+ return TRUE;
+}
- add_mtp_track_to_db (source, db, track);
- g_object_unref (db);
+static void
+prepare_encoder_sink_cb (RBEncoderFactory *factory,
+ const char *stream_uri,
+ GObject *sink,
+ RBMtpSource *source)
+{
+ RBMtpSourcePrivate *priv = MTP_SOURCE_GET_PRIVATE (source);
+ RhythmDBEntry *entry;
+ RhythmDB *db;
+ LIBMTP_track_t *track;
+ char **bits;
+ char *extension;
+ LIBMTP_filetype_t filetype;
+ gulong track_id;
+ GDate d;
+
+ /* make sure this stream is for a file on our device */
+ if (g_str_has_prefix (stream_uri, "xrbmtp://") == FALSE)
+ return;
+
+ /* extract the entry ID, extension, and MTP filetype from the URI */
+ bits = g_strsplit (stream_uri + strlen ("xrbmtp://"), "/", 3);
+ track_id = strtoul (bits[0], NULL, 0);
+ extension = g_strdup (bits[1]);
+ filetype = strtoul (bits[2], NULL, 0);
+ g_strfreev (bits);
+
+ db = get_db_for_source (source);
+ entry = rhythmdb_entry_lookup_by_id (db, track_id);
+ g_object_unref (db);
+ if (entry == NULL)
+ return;
+
+ track = LIBMTP_new_track_t ();
+ track->title = rhythmdb_entry_dup_string (entry, RHYTHMDB_PROP_TITLE);
+ track->album = rhythmdb_entry_dup_string (entry, RHYTHMDB_PROP_ALBUM);
+ track->artist = rhythmdb_entry_dup_string (entry, RHYTHMDB_PROP_ARTIST);
+ track->genre = rhythmdb_entry_dup_string (entry, RHYTHMDB_PROP_GENRE);
+
+ /* build up device filename; may want to reconsider if we start creating folders */
+ track->filename = g_strdup_printf ("%s - %s.%s",
+ rhythmdb_entry_get_string (entry, RHYTHMDB_PROP_ARTIST),
+ rhythmdb_entry_get_string (entry, RHYTHMDB_PROP_TITLE),
+ extension);
+ g_free (extension);
+
+ rb_sanitize_path_for_msdos_filesystem (track->filename);
+
+ if (rhythmdb_entry_get_ulong (entry, RHYTHMDB_PROP_DATE) > 0) {
+ g_date_set_julian (&d, rhythmdb_entry_get_ulong (entry, RHYTHMDB_PROP_DATE));
+ track->date = gdate_to_char (&d);
}
+ track->tracknumber = rhythmdb_entry_get_ulong (entry, RHYTHMDB_PROP_TRACK_NUMBER);
+ track->duration = rhythmdb_entry_get_ulong (entry, RHYTHMDB_PROP_DURATION) * 1000;
+ track->rating = rhythmdb_entry_get_double (entry, RHYTHMDB_PROP_RATING) * 20;
+ track->usecount = rhythmdb_entry_get_ulong (entry, RHYTHMDB_PROP_PLAY_COUNT);
- return FALSE;
+ track->filetype = filetype;
+
+ g_object_set (sink, "device-thread", priv->device_thread, "mtp-track", track, NULL);
+ rhythmdb_entry_unref (entry);
+
+ g_hash_table_insert (priv->track_transfer_map, g_strdup (stream_uri), track);
}
static char *
@@ -1057,12 +991,34 @@ impl_build_dest_uri (RBRemovableMediaSource *source,
const char *mimetype,
const char *extension)
{
- char* file = g_strdup_printf ("%s/%s-%s.%s", g_get_tmp_dir (),
- rhythmdb_entry_dup_string (entry, RHYTHMDB_PROP_ARTIST),
- rhythmdb_entry_dup_string (entry, RHYTHMDB_PROP_TITLE),
- extension);
- char* uri = g_filename_to_uri (file, NULL, NULL);
- g_free (file);
+ gulong id;
+ char *uri;
+ LIBMTP_filetype_t filetype;
+
+ if (mimetype == NULL) {
+ mimetype = rhythmdb_entry_get_string (entry, RHYTHMDB_PROP_MIMETYPE);
+ }
+ filetype = mimetype_to_filetype (RB_MTP_SOURCE (source), mimetype);
+ rb_debug ("using libmtp filetype %d (%s) for source media type %s",
+ filetype,
+ LIBMTP_Get_Filetype_Description (filetype),
+ mimetype);
+
+ /* the prepare-sink callback needs the entry ID to set up the
+ * upload data, and we want to use the supplied extension for
+ * the filename on the device.
+ *
+ * this is pretty ugly - it'd be much nicer to have a source-defined
+ * structure that got passed around (or was accessible from) the various
+ * hooks and methods called during the track transfer process. probably
+ * something to address in my horribly stalled track transfer rewrite..
+ *
+ * the structure would either be created when queuing the track for transfer,
+ * or here; passed to any prepare-source or prepare-sink callbacks for the
+ * encoder; and then passed to whatever gets called when the transfer is complete.
+ */
+ id = rhythmdb_entry_get_ulong (entry, RHYTHMDB_PROP_ENTRY_ID);
+ uri = g_strdup_printf ("xrbmtp://%lu/%s/%d", id, extension, filetype);
return uri;
}
@@ -1075,13 +1031,7 @@ artwork_notify_cb (RhythmDB *db,
{
RBMtpSourcePrivate *priv = MTP_SOURCE_GET_PRIVATE (source);
GdkPixbuf *pixbuf;
- LIBMTP_album_t *album;
- LIBMTP_filesampledata_t *albumart;
- GError *error = NULL;
const char *album_name;
- char *image_data;
- gsize image_size;
- int ret;
album_name = rhythmdb_entry_get_string (entry, RHYTHMDB_PROP_ALBUM);
@@ -1094,39 +1044,9 @@ artwork_notify_cb (RhythmDB *db,
pixbuf = GDK_PIXBUF (g_value_get_object (metadata));
- /* we should already have created an album object */
- album = g_hash_table_lookup (priv->album_map, album_name);
- if (album == NULL) {
- rb_debug ("couldn't find an album for %s", album_name);
- return;
- }
-
- /* probably should scale the image down, since some devices have a size limit and they all have
- * tiny displays anyway.
- */
-
- if (gdk_pixbuf_save_to_buffer (pixbuf, &image_data, &image_size, "jpeg", &error, NULL) == FALSE) {
- rb_debug ("unable to convert album art image to a JPEG buffer: %s", error->message);
- g_error_free (error);
- return;
- }
-
- albumart = LIBMTP_new_filesampledata_t ();
- albumart->filetype = LIBMTP_FILETYPE_JPEG;
- albumart->data = image_data;
- albumart->size = image_size;
-
- ret = LIBMTP_Send_Representative_Sample (priv->device, album->album_id, albumart);
- if (ret != 0) {
- report_libmtp_errors (priv->device, TRUE);
- } else {
- rb_debug ("successfully set album art for %s (%" G_GSIZE_FORMAT " bytes)", album_name, image_size);
- }
+ rb_mtp_thread_set_album_image (priv->device_thread, album_name, pixbuf);
- /* libmtp will try to free this if we don't clear the pointer */
- albumart->data = NULL;
- LIBMTP_destroy_filesampledata_t (albumart);
- g_free (image_data);
+ g_object_unref (pixbuf); /* ? */
}
static void
@@ -1151,8 +1071,8 @@ prepare_source (RBMtpSource *source, const char *stream_uri, GObject *src)
return;
}
- rb_debug ("setting device %p for stream %s", priv->device, stream_uri);
- g_object_set (src, "device", priv->device, NULL);
+ rb_debug ("setting device-thread for stream %s", stream_uri);
+ g_object_set (src, "device-thread", priv->device_thread, NULL);
rhythmdb_entry_unref (entry);
}
diff --git a/plugins/mtpdevice/rb-mtp-source.h b/plugins/mtpdevice/rb-mtp-source.h
index 7c08ab3..f49f985 100644
--- a/plugins/mtpdevice/rb-mtp-source.h
+++ b/plugins/mtpdevice/rb-mtp-source.h
@@ -55,9 +55,9 @@ typedef struct
} RBMtpSourceClass;
#if defined(HAVE_GUDEV)
-RBSource * rb_mtp_source_new (RBShell *shell, LIBMTP_mtpdevice_t *device);
+RBSource * rb_mtp_source_new (RBShell *shell, LIBMTP_raw_device_t *device);
#else
-RBSource * rb_mtp_source_new (RBShell *shell, LIBMTP_mtpdevice_t *device, const char *udi);
+RBSource * rb_mtp_source_new (RBShell *shell, LIBMTP_raw_device_t *device, const char *udi);
#endif
GType rb_mtp_source_get_type (void);
diff --git a/plugins/mtpdevice/rb-mtp-thread.c b/plugins/mtpdevice/rb-mtp-thread.c
new file mode 100644
index 0000000..61c5fde
--- /dev/null
+++ b/plugins/mtpdevice/rb-mtp-thread.c
@@ -0,0 +1,809 @@
+/*
+ * Copyright (C) 2009 Jonathan Matthew <jonathan d14n org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * The Rhythmbox authors hereby grant permission for non-GPL compatible
+ * GStreamer plugins to be used and distributed together with GStreamer
+ * and Rhythmbox. This permission is above and beyond the permissions granted
+ * by the GPL license by which Rhythmbox is covered. If you modify this code
+ * you may extend this exception to your version of the code, but you are not
+ * obligated to do so. If you do not wish to do so, delete this exception
+ * statement from your version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ */
+
+#include <config.h>
+
+#include <string.h>
+
+#include <glib.h>
+#include <glib/gi18n.h>
+#include <gtk/gtk.h>
+
+#include "rb-mtp-thread.h"
+#include "rb-file-helpers.h"
+#include "rb-dialog.h"
+#include "rb-debug.h"
+
+G_DEFINE_TYPE(RBMtpThread, rb_mtp_thread, G_TYPE_OBJECT)
+
+
+typedef struct {
+ enum {
+ OPEN_DEVICE = 1,
+ CLOSE_DEVICE,
+ SET_DEVICE_NAME,
+
+ ADD_TO_ALBUM,
+ REMOVE_FROM_ALBUM,
+ SET_ALBUM_IMAGE,
+
+ GET_TRACK_LIST,
+ DELETE_TRACK,
+ UPLOAD_TRACK,
+ DOWNLOAD_TRACK
+ } task;
+
+ LIBMTP_raw_device_t *raw_device;
+ LIBMTP_track_t *track;
+ uint32_t track_id;
+ uint32_t storage_id;
+ char *album;
+ char *filename;
+ GdkPixbuf *image;
+ char *name;
+
+ gpointer callback;
+ gpointer user_data;
+ GDestroyNotify destroy_data;
+} RBMtpThreadTask;
+
+static char *
+task_name (RBMtpThreadTask *task)
+{
+ switch (task->task) {
+ case OPEN_DEVICE: return g_strdup ("open device");
+ case CLOSE_DEVICE: return g_strdup ("close device");
+ case SET_DEVICE_NAME: return g_strdup_printf ("set device name to %s", task->name);
+
+ case ADD_TO_ALBUM: return g_strdup_printf ("add track %u to album %s", task->track_id, task->album);
+ case REMOVE_FROM_ALBUM: return g_strdup_printf ("remove track %u from album %s", task->track_id, task->album);
+ case SET_ALBUM_IMAGE: return g_strdup_printf ("set image for album %s", task->album);
+
+ case GET_TRACK_LIST: return g_strdup ("get track list");
+ case DELETE_TRACK: return g_strdup_printf ("delete track %u", task->track_id);
+ case UPLOAD_TRACK: return g_strdup_printf ("upload track from %s", task->filename);
+ case DOWNLOAD_TRACK: return g_strdup_printf ("download track %u to %s",
+ task->track_id,
+ task->filename[0] ? task->filename : "<temporary>");
+ default: return g_strdup_printf ("unknown task type %d", task->task);
+ }
+}
+
+static RBMtpThreadTask *
+create_task (int tasktype)
+{
+ RBMtpThreadTask *task = g_slice_new0 (RBMtpThreadTask);
+ task->task = tasktype;
+ return task;
+}
+
+static void
+destroy_task (RBMtpThreadTask *task)
+{
+ /* don't think we ever own the track structure here;
+ * we only have it for uploads, and then we pass it back
+ * to the callback.
+ */
+
+ g_free (task->album);
+ g_free (task->filename);
+ g_free (task->name);
+
+ if (task->image) {
+ g_object_unref (task->image);
+ }
+
+ if (task->destroy_data) {
+ task->destroy_data (task->user_data);
+ }
+
+ g_slice_free (RBMtpThreadTask, task);
+}
+
+
+static void
+queue_task (RBMtpThread *thread, RBMtpThreadTask *task)
+{
+ char *name = task_name (task);
+ rb_debug ("queueing task: %s", name);
+ g_free (name);
+
+ g_async_queue_push (thread->queue, task);
+}
+
+static void
+open_device (RBMtpThread *thread, RBMtpThreadTask *task)
+{
+ RBMtpOpenCallback cb = task->callback;
+
+ /* open the device */
+ thread->device = LIBMTP_Open_Raw_Device (task->raw_device);
+ if (thread->device == NULL) {
+ /* hm. any error information we can give here? */
+ /* maybe retry a few times since apparently that can help? */
+ cb (NULL, task->user_data);
+ return;
+ }
+
+ cb (thread->device, task->user_data);
+}
+
+static LIBMTP_album_t *
+add_track_to_album (RBMtpThread *thread, const char *album_name, uint32_t track_id, uint32_t storage_id, gboolean *new_album)
+{
+ LIBMTP_album_t *album;
+
+ album = g_hash_table_lookup (thread->albums, album_name);
+ if (album != NULL) {
+ /* add track to album */
+ album->tracks = realloc (album->tracks, sizeof(uint32_t) * (album->no_tracks+1));
+ album->tracks[album->no_tracks] = track_id;
+ album->no_tracks++;
+ rb_debug ("adding track ID %d to album ID %d; now has %d tracks",
+ track_id,
+ album->album_id,
+ album->no_tracks);
+
+ if (new_album != NULL) {
+ *new_album = FALSE;
+ }
+ } else {
+ /* add new album */
+ album = LIBMTP_new_album_t ();
+ album->name = strdup (album_name);
+ album->no_tracks = 1;
+ album->tracks = malloc (sizeof(uint32_t));
+ album->tracks[0] = track_id;
+ album->storage_id = storage_id;
+
+ rb_debug ("creating new album (%s) for track ID %d", album->name, track_id);
+
+ g_hash_table_insert (thread->albums, album->name, album);
+ if (new_album != NULL) {
+ *new_album = TRUE;
+ }
+ }
+
+ return album;
+}
+
+static void
+write_album_to_device (RBMtpThread *thread, LIBMTP_album_t *album, gboolean new_album)
+{
+ if (new_album) {
+ if (LIBMTP_Create_New_Album (thread->device, album) != 0) {
+ LIBMTP_destroy_album_t (album);
+ rb_debug ("LIBMTP_Create_New_Album failed..");
+ rb_mtp_thread_report_errors (thread, FALSE);
+ }
+ } else {
+ if (LIBMTP_Update_Album (thread->device, album) != 0) {
+ rb_debug ("LIBMTP_Update_Album failed..");
+ rb_mtp_thread_report_errors (thread, FALSE);
+ }
+ }
+}
+
+static void
+add_track_to_album_and_update (RBMtpThread *thread, RBMtpThreadTask *task)
+{
+ LIBMTP_album_t *album;
+ gboolean new_album = FALSE;
+
+ album = add_track_to_album (thread, task->album, task->track_id, task->storage_id, &new_album);
+ write_album_to_device (thread, album, new_album);
+}
+
+static void
+remove_track_from_album (RBMtpThread *thread, RBMtpThreadTask *task)
+{
+ LIBMTP_album_t *album;
+ int i;
+
+ album = g_hash_table_lookup (thread->albums, task->album);
+ if (album == NULL) {
+ rb_debug ("Couldn't find an album for %s", task->album);
+ return;
+ }
+
+ for (i = 0; i < album->no_tracks; i++) {
+ if (album->tracks[i] == task->track_id) {
+ break;
+ }
+ }
+
+ if (i == album->no_tracks) {
+ rb_debug ("Couldn't find track %d in album %d", task->track_id, album->album_id);
+ return;
+ }
+
+ memmove (album->tracks + i, album->tracks + i + 1, album->no_tracks - (i+1));
+ album->no_tracks--;
+
+ if (album->no_tracks == 0) {
+ rb_debug ("deleting empty album %d", album->album_id);
+ if (LIBMTP_Delete_Object (thread->device, album->album_id) != 0) {
+ rb_mtp_thread_report_errors (thread, FALSE);
+ }
+ g_hash_table_remove (thread->albums, task->album);
+ } else {
+ rb_debug ("updating album %d: %d tracks remaining", album->album_id, album->no_tracks);
+ if (LIBMTP_Update_Album (thread->device, album) != 0) {
+ rb_mtp_thread_report_errors (thread, FALSE);
+ }
+ }
+}
+
+static void
+set_album_image (RBMtpThread *thread, RBMtpThreadTask *task)
+{
+ LIBMTP_filesampledata_t *albumart;
+ LIBMTP_album_t *album;
+ GError *error = NULL;
+ char *image_data;
+ gsize image_size;
+ int ret;
+
+ album = g_hash_table_lookup (thread->albums, task->album);
+ if (album == NULL) {
+ rb_debug ("Couldn't find an album for %s", task->album);
+ return;
+ }
+
+ /* probably should scale the image down, since some devices have a size limit and they all have
+ * tiny displays anyway.
+ */
+
+ if (gdk_pixbuf_save_to_buffer (task->image, &image_data, &image_size, "jpeg", &error, NULL) == FALSE) {
+ rb_debug ("unable to convert album art image to a JPEG buffer: %s", error->message);
+ g_error_free (error);
+ return;
+ }
+
+ albumart = LIBMTP_new_filesampledata_t ();
+ albumart->filetype = LIBMTP_FILETYPE_JPEG;
+ albumart->data = image_data;
+ albumart->size = image_size;
+
+ ret = LIBMTP_Send_Representative_Sample (thread->device, album->album_id, albumart);
+ if (ret != 0) {
+ rb_mtp_thread_report_errors (thread, TRUE);
+ } else {
+ rb_debug ("successfully set album art for %s (%" G_GSIZE_FORMAT " bytes)", task->album, image_size);
+ }
+
+ /* libmtp will try to free this if we don't clear the pointer */
+ albumart->data = NULL;
+ LIBMTP_destroy_filesampledata_t (albumart);
+}
+
+static void
+get_track_list (RBMtpThread *thread, RBMtpThreadTask *task)
+{
+ RBMtpTrackListCallback cb = task->callback;
+ gboolean device_forgets_albums = TRUE;
+ GHashTable *update_albums = NULL;
+ LIBMTP_track_t *tracks = NULL;
+ LIBMTP_album_t *albums;
+ LIBMTP_album_t *album;
+
+ /* get all the albums */
+ albums = LIBMTP_Get_Album_List (thread->device);
+ rb_mtp_thread_report_errors (thread, FALSE);
+ if (albums != NULL) {
+ LIBMTP_album_t *album;
+
+ for (album = albums; album != NULL; album = album->next) {
+ if (album->name == NULL)
+ continue;
+
+ rb_debug ("album: %s, %d tracks", album->name, album->no_tracks);
+ g_hash_table_insert (thread->albums, album->name, album);
+ if (album->no_tracks != 0) {
+ device_forgets_albums = FALSE;
+ }
+ }
+
+ if (device_forgets_albums) {
+ rb_debug ("stupid mtp device detected. will rebuild all albums.");
+ }
+ } else {
+ rb_debug ("No albums");
+ device_forgets_albums = FALSE;
+ }
+
+ tracks = LIBMTP_Get_Tracklisting_With_Callback (thread->device, NULL, NULL);
+ rb_mtp_thread_report_errors (thread, FALSE);
+ if (tracks == NULL) {
+ rb_debug ("no tracks on the device");
+ } else if (device_forgets_albums) {
+ LIBMTP_track_t *track;
+
+ rb_debug ("rebuilding albums");
+ update_albums = g_hash_table_new (g_direct_hash, g_direct_equal);
+ for (track = tracks; track != NULL; track = track->next) {
+ if (track->album != NULL) {
+ gboolean new_album = FALSE;
+ album = add_track_to_album (thread, track->album, track->item_id, track->storage_id, &new_album);
+ g_hash_table_insert (update_albums, album, GINT_TO_POINTER (new_album));
+ }
+ }
+ rb_debug ("finished rebuilding albums");
+ }
+
+ cb (tracks, task->user_data);
+ /* the callback owns the tracklist */
+
+ if (device_forgets_albums) {
+ GHashTableIter iter;
+ gpointer album_ptr;
+ gpointer new_album_ptr;
+
+ rb_debug ("writing rebuilt albums back to the device");
+ g_hash_table_iter_init (&iter, update_albums);
+ while (g_hash_table_iter_next (&iter, &album_ptr, &new_album_ptr)) {
+ album = album_ptr;
+ rb_debug ("writing album \"%s\"", album->name);
+ write_album_to_device (thread, album, GPOINTER_TO_INT (new_album_ptr));
+ }
+ g_hash_table_destroy (update_albums);
+
+ rb_debug ("removing remaining empty albums");
+ g_hash_table_iter_init (&iter, thread->albums);
+ while (g_hash_table_iter_next (&iter, NULL, &album_ptr)) {
+ int ret;
+
+ album = album_ptr;
+ if (album->no_tracks == 0) {
+ rb_debug ("pruning empty album \"%s\"", album->name);
+ ret = LIBMTP_Delete_Object (thread->device, album->album_id);
+ if (ret != 0) {
+ rb_mtp_thread_report_errors (thread, FALSE);
+ }
+ g_hash_table_iter_remove (&iter);
+ }
+ }
+
+ rb_debug ("finished updating albums on the device");
+ }
+}
+
+static void
+download_track (RBMtpThread *thread, RBMtpThreadTask *task)
+{
+ LIBMTP_file_t *fileinfo;
+ LIBMTP_error_t *stack;
+ GError *error = NULL;
+ GFile *dir;
+ RBMtpDownloadCallback cb = (RBMtpDownloadCallback) task->callback;
+
+ /* first, check there's enough space to copy it */
+ fileinfo = LIBMTP_Get_Filemetadata (thread->device, task->track_id);
+ if (fileinfo == NULL) {
+ stack = LIBMTP_Get_Errorstack (thread->device);
+ rb_debug ("unable to get track metadata for %u: %s", task->track_id, stack->error_text);
+ error = g_error_new (RB_MTP_THREAD_ERROR,
+ RB_MTP_THREAD_ERROR_GET_TRACK,
+ _("Unable to copy file from MTP device: %s"),
+ stack->error_text);
+ LIBMTP_Clear_Errorstack (thread->device);
+
+ cb (task->track_id, NULL, error, task->user_data);
+ g_error_free (error);
+ return;
+ }
+
+ if (task->filename[0] == '\0') {
+ dir = g_file_new_for_path (g_get_tmp_dir ());
+ } else {
+ GFile *file = g_file_new_for_path (task->filename);
+ dir = g_file_get_parent (file);
+ g_object_unref (file);
+ }
+ rb_debug ("checking for %" G_GINT64_FORMAT " bytes available", fileinfo->filesize);
+ if (rb_check_dir_has_space (dir, fileinfo->filesize) == FALSE) {
+ char *dpath = g_file_get_path (dir);
+ rb_debug ("not enough space in %s", dpath);
+ error = g_error_new (RB_MTP_THREAD_ERROR, RB_MTP_THREAD_ERROR_NO_SPACE,
+ _("Not enough space in %s"), dpath);
+ g_free (dpath);
+ }
+ LIBMTP_destroy_file_t (fileinfo);
+ g_object_unref (dir);
+
+ if (error != NULL) {
+ rb_debug ("bailing out due to error: %s", error->message);
+ cb (task->track_id, NULL, error, task->user_data);
+ g_error_free (error);
+ return;
+ }
+
+ if (task->filename[0] == '\0') {
+ /* download to a temporary file */
+ int fd;
+ GError *tmperror = NULL;
+
+ g_free (task->filename);
+ fd = g_file_open_tmp ("rb-mtp-temp-XXXXXX", &task->filename, &tmperror);
+ if (fd == -1) {
+ rb_debug ("unable to open temporary file: %s", tmperror->message);
+ error = g_error_new (RB_MTP_THREAD_ERROR,
+ RB_MTP_THREAD_ERROR_TEMPFILE,
+ _("Unable to open temporary file: %s"),
+ tmperror->message);
+ g_error_free (tmperror);
+
+ cb (task->track_id, NULL, error, task->user_data);
+ g_error_free (error);
+ return;
+ } else {
+ rb_debug ("downloading track %u to file descriptor %d", task->track_id, fd);
+ if (LIBMTP_Get_Track_To_File_Descriptor (thread->device, task->track_id, fd, NULL, NULL)) {
+ stack = LIBMTP_Get_Errorstack (thread->device);
+ rb_debug ("unable to retrieve track %u: %s", task->track_id, stack->error_text);
+ error = g_error_new (RB_MTP_THREAD_ERROR, RB_MTP_THREAD_ERROR_GET_TRACK,
+ _("Unable to copy file from MTP device: %s"),
+ stack->error_text);
+ LIBMTP_Clear_Errorstack (thread->device);
+
+ cb (task->track_id, NULL, error, task->user_data);
+ g_error_free (error);
+ close (fd);
+ remove (task->filename);
+ return;
+ }
+ rb_debug ("done downloading track");
+
+ close (fd);
+ }
+ } else {
+ if (LIBMTP_Get_Track_To_File (thread->device, task->track_id, task->filename, NULL, NULL)) {
+ stack = LIBMTP_Get_Errorstack (thread->device);
+ error = g_error_new (RB_MTP_THREAD_ERROR, RB_MTP_THREAD_ERROR_GET_TRACK,
+ _("Unable to copy file from MTP device: %s"),
+ stack->error_text);
+ LIBMTP_Clear_Errorstack (thread->device);
+
+ cb (task->track_id, NULL, error, task->user_data);
+ g_error_free (error);
+ return;
+ }
+ }
+
+ cb (task->track_id, task->filename, NULL, task->user_data);
+}
+
+static void
+upload_track (RBMtpThread *thread, RBMtpThreadTask *task)
+{
+ RBMtpUploadCallback cb = (RBMtpUploadCallback) task->callback;
+ LIBMTP_error_t *stack;
+ GError *error = NULL;
+
+ if (LIBMTP_Send_Track_From_File (thread->device, task->filename, task->track, NULL, NULL)) {
+ stack = LIBMTP_Get_Errorstack (thread->device);
+ rb_debug ("unable to send track: %s", stack->error_text);
+ error = g_error_new (RB_MTP_THREAD_ERROR, RB_MTP_THREAD_ERROR_SEND_TRACK,
+ _("Unable to send file to MTP device: %s"),
+ stack->error_text);
+ LIBMTP_Clear_Errorstack (thread->device);
+ task->track->item_id = 0; /* is this actually an invalid item ID? */
+ }
+ cb (task->track, error, task->user_data);
+ g_clear_error (&error);
+}
+
+static gboolean
+run_task (RBMtpThread *thread, RBMtpThreadTask *task)
+{
+ char *name = task_name (task);
+ rb_debug ("running task: %s", name);
+ g_free (name);
+
+ switch (task->task) {
+ case OPEN_DEVICE:
+ open_device (thread, task);
+ break;
+
+ case CLOSE_DEVICE:
+ return TRUE;
+
+ case SET_DEVICE_NAME:
+ if (LIBMTP_Set_Friendlyname (thread->device, task->name)) {
+ rb_mtp_thread_report_errors (thread, TRUE);
+ }
+ break;
+
+ case ADD_TO_ALBUM:
+ add_track_to_album_and_update (thread, task);
+ break;
+
+ case REMOVE_FROM_ALBUM:
+ remove_track_from_album (thread, task);
+ break;
+
+ case SET_ALBUM_IMAGE:
+ set_album_image (thread, task);
+ break;
+
+ case GET_TRACK_LIST:
+ get_track_list (thread, task);
+ break;
+
+ case DELETE_TRACK:
+ if (LIBMTP_Delete_Object (thread->device, task->track_id)) {
+ rb_mtp_thread_report_errors (thread, TRUE);
+ }
+ break;
+
+ case UPLOAD_TRACK:
+ upload_track (thread, task);
+ break;
+
+ case DOWNLOAD_TRACK:
+ download_track (thread, task);
+ break;
+
+ default:
+ g_assert_not_reached ();
+ }
+
+ return FALSE;
+}
+
+static gpointer
+task_thread (RBMtpThread *thread)
+{
+ RBMtpThreadTask *task;
+ gboolean quit = FALSE;
+
+ rb_debug ("MTP device worker thread starting");
+ while (quit == FALSE) {
+
+ task = g_async_queue_pop (thread->queue);
+ quit = run_task (thread, task);
+ destroy_task (task);
+ }
+
+ rb_debug ("MTP device worker thread exiting");
+
+ /* clean up any queued tasks */
+ while ((task = g_async_queue_try_pop (thread->queue)) != NULL)
+ destroy_task (task);
+
+ return NULL;
+}
+
+/* callable interface */
+
+void
+rb_mtp_thread_open_device (RBMtpThread *thread,
+ LIBMTP_raw_device_t *raw_device,
+ RBMtpOpenCallback callback,
+ gpointer data,
+ GDestroyNotify destroy_data)
+{
+ RBMtpThreadTask *task = create_task (OPEN_DEVICE);
+ task->raw_device = raw_device;
+ task->callback = callback;
+ task->user_data = data;
+ task->destroy_data = destroy_data;
+ queue_task (thread, task);
+}
+
+void
+rb_mtp_thread_set_device_name (RBMtpThread *thread, const char *name)
+{
+ RBMtpThreadTask *task = create_task (SET_DEVICE_NAME);
+ task->name = g_strdup (name);
+ queue_task (thread, task);
+}
+
+void
+rb_mtp_thread_add_to_album (RBMtpThread *thread, LIBMTP_track_t *track, const char *album)
+{
+ RBMtpThreadTask *task = create_task (ADD_TO_ALBUM);
+ task->track_id = track->item_id;
+ task->storage_id = track->storage_id;
+ task->album = g_strdup (album);
+ queue_task (thread, task);
+}
+
+void
+rb_mtp_thread_remove_from_album (RBMtpThread *thread, LIBMTP_track_t *track, const char *album)
+{
+ RBMtpThreadTask *task = create_task (REMOVE_FROM_ALBUM);
+ task->track_id = track->item_id;
+ task->storage_id = track->storage_id;
+ task->album = g_strdup (album);
+ queue_task (thread, task);
+}
+
+void
+rb_mtp_thread_set_album_image (RBMtpThread *thread, const char *album, GdkPixbuf *image)
+{
+ RBMtpThreadTask *task = create_task (SET_ALBUM_IMAGE);
+ task->album = g_strdup (album);
+ task->image = g_object_ref (image);
+ queue_task (thread, task);
+}
+
+void
+rb_mtp_thread_get_track_list (RBMtpThread *thread,
+ RBMtpTrackListCallback callback,
+ gpointer data,
+ GDestroyNotify destroy_data)
+{
+ RBMtpThreadTask *task = create_task (GET_TRACK_LIST);
+ task->callback = callback;
+ task->user_data = data;
+ task->destroy_data = destroy_data;
+ queue_task (thread, task);
+}
+
+void
+rb_mtp_thread_delete_track (RBMtpThread *thread, LIBMTP_track_t *track)
+{
+ RBMtpThreadTask *task = create_task (DELETE_TRACK);
+ task->track_id = track->item_id;
+ task->storage_id = track->storage_id;
+ queue_task (thread, task);
+}
+
+void
+rb_mtp_thread_upload_track (RBMtpThread *thread,
+ LIBMTP_track_t *track,
+ const char *filename,
+ RBMtpUploadCallback func,
+ gpointer data,
+ GDestroyNotify destroy_data)
+{
+ RBMtpThreadTask *task = create_task (UPLOAD_TRACK);
+ task->track = track;
+ task->filename = g_strdup (filename);
+ task->callback = func;
+ task->user_data = data;
+ task->destroy_data = destroy_data;
+ queue_task (thread, task);
+}
+
+void
+rb_mtp_thread_download_track (RBMtpThread *thread,
+ uint32_t track_id,
+ const char *filename,
+ RBMtpDownloadCallback func,
+ gpointer data,
+ GDestroyNotify destroy_data)
+{
+ RBMtpThreadTask *task = create_task (DOWNLOAD_TRACK);
+ task->track_id = track_id;
+ task->filename = g_strdup (filename);
+ task->callback = func;
+ task->user_data = data;
+ task->destroy_data = destroy_data;
+ queue_task (thread, task);
+}
+
+void
+rb_mtp_thread_report_errors (RBMtpThread *thread, gboolean use_dialog)
+{
+ LIBMTP_error_t *stack;
+
+ for (stack = LIBMTP_Get_Errorstack (thread->device); stack != NULL; stack = stack->next) {
+ if (use_dialog) {
+ GDK_THREADS_ENTER ();
+ rb_error_dialog (NULL, _("Media player device error"), "%s", stack->error_text);
+ GDK_THREADS_LEAVE ();
+
+ /* only display one dialog box per error */
+ use_dialog = FALSE;
+ } else {
+ g_warning ("libmtp error: %s", stack->error_text);
+ }
+ }
+
+ LIBMTP_Clear_Errorstack (thread->device);
+}
+
+/* GObject things */
+
+static void
+impl_finalize (GObject *object)
+{
+ RBMtpThread *thread = RB_MTP_THREAD (object);
+ RBMtpThreadTask *task;
+
+ rb_debug ("joining MTP worker thread");
+ task = create_task (CLOSE_DEVICE);
+ queue_task (thread, task);
+ g_thread_join (thread->thread);
+ rb_debug ("MTP worker thread exited");
+
+ g_async_queue_unref (thread->queue);
+
+ g_hash_table_destroy (thread->albums);
+
+ LIBMTP_Release_Device (thread->device);
+
+ G_OBJECT_CLASS (rb_mtp_thread_parent_class)->finalize (object);
+}
+
+static void
+rb_mtp_thread_init (RBMtpThread *thread)
+{
+ thread->queue = g_async_queue_new ();
+
+ thread->albums = g_hash_table_new_full (g_str_hash, g_str_equal, NULL, (GDestroyNotify) LIBMTP_destroy_album_t);
+
+ thread->thread = g_thread_create ((GThreadFunc) task_thread, thread, TRUE, NULL); /* XXX should handle errors i guess */
+}
+
+static void
+rb_mtp_thread_class_init (RBMtpThreadClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+ object_class->finalize = impl_finalize;
+}
+
+RBMtpThread *
+rb_mtp_thread_new (void)
+{
+ return RB_MTP_THREAD (g_object_new (RB_TYPE_MTP_THREAD, NULL));
+}
+
+GQuark
+rb_mtp_thread_error_quark (void)
+{
+ static GQuark quark = 0;
+ if (!quark)
+ quark = g_quark_from_static_string ("rb_mtp_thread_error");
+
+ return quark;
+}
+
+#define ENUM_ENTRY(NAME, DESC) { NAME, "" #NAME "", DESC }
+
+GType
+rb_mtp_thread_error_get_type (void)
+{
+ static GType etype = 0;
+
+ if (etype == 0) {
+ static const GEnumValue values[] = {
+ ENUM_ENTRY (RB_MTP_THREAD_ERROR_NO_SPACE, "Not enough space to download track"),
+ ENUM_ENTRY (RB_MTP_THREAD_ERROR_TEMPFILE, "Unable to create temporary file"),
+ ENUM_ENTRY (RB_MTP_THREAD_ERROR_GET_TRACK, "Unable to retrieve track"),
+ { 0, 0, 0 }
+ };
+
+ etype = g_enum_register_static ("RBMTPThreadError", values);
+ }
+
+ return etype;
+}
+
diff --git a/plugins/mtpdevice/rb-mtp-thread.h b/plugins/mtpdevice/rb-mtp-thread.h
new file mode 100644
index 0000000..c03597a
--- /dev/null
+++ b/plugins/mtpdevice/rb-mtp-thread.h
@@ -0,0 +1,125 @@
+/*
+ * Copyright (C) 2009 Jonathan Matthew <jonathan d14n org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * The Rhythmbox authors hereby grant permission for non-GPL compatible
+ * GStreamer plugins to be used and distributed together with GStreamer
+ * and Rhythmbox. This permission is above and beyond the permissions granted
+ * by the GPL license by which Rhythmbox is covered. If you modify this code
+ * you may extend this exception to your version of the code, but you are not
+ * obligated to do so. If you do not wish to do so, delete this exception
+ * statement from your version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ */
+
+#include <libmtp.h>
+
+#include <gio/gio.h>
+#include <glib.h>
+#include <glib-object.h>
+#include <gdk-pixbuf/gdk-pixbuf.h>
+
+/*
+ * Worker thread for dealing with MTP devices.
+ * libmtp isn't thread-aware, and some operations are pretty slow
+ * (getting track listings, sending and retrieving tracks), so
+ * we do everything except the initial setup in a dedicated thread.
+ */
+
+#ifndef __RB_MTP_THREAD_H
+#define __RB_MTP_THREAD_H
+
+typedef enum
+{
+ RB_MTP_THREAD_ERROR_NO_SPACE,
+ RB_MTP_THREAD_ERROR_TEMPFILE,
+ RB_MTP_THREAD_ERROR_GET_TRACK,
+ RB_MTP_THREAD_ERROR_SEND_TRACK
+} RBMtpThreadError;
+
+GType rb_mtp_thread_error_get_type (void);
+#define RB_TYPE_MTP_THREAD_ERROR (rb_mtp_thread_error_get_type ())
+GQuark rb_mtp_thread_error_quark (void);
+#define RB_MTP_THREAD_ERROR rb_mtp_thread_error_quark ()
+
+#define RB_TYPE_MTP_THREAD (rb_mtp_thread_get_type ())
+#define RB_MTP_THREAD(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), RB_TYPE_MTP_THREAD, RBMtpThread))
+#define RB_MTP_THREAD_CLASS(k) (G_TYPE_CHECK_CLASS_CAST((k), RB_TYPE_MTP_THREAD, RBMtpThreadClass))
+#define RB_IS_MTP_THREAD(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), RB_TYPE_MTP_THREAD))
+#define RB_IS_MTP_THREAD_CLASS(k) (G_TYPE_CHECK_CLASS_TYPE ((k), RB_TYPE_MTP_THREAD))
+#define RB_MTP_THREAD_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), RB_TYPE_MTP_THREAD, RBMtpThreadClass))
+
+typedef struct
+{
+ GObject parent;
+ LIBMTP_mtpdevice_t *device;
+ GHashTable *albums;
+
+ GThread *thread;
+ GAsyncQueue *queue;
+} RBMtpThread;
+
+typedef struct
+{
+ GObjectClass parent;
+} RBMtpThreadClass;
+
+/* callback types */
+typedef void (*RBMtpOpenCallback) (LIBMTP_mtpdevice_t *device, gpointer user_data);
+typedef void (*RBMtpTrackListCallback) (LIBMTP_track_t *tracklist, gpointer user_data);
+typedef void (*RBMtpUploadCallback) (LIBMTP_track_t *track, GError *error, gpointer user_data);
+typedef void (*RBMtpDownloadCallback) (uint32_t track_id, const char *filename, GError *error, gpointer user_data);
+typedef void (*RBMtpCreatePlaylistCallback) (LIBMTP_playlist_t *playlist, gpointer user_data);
+
+GType rb_mtp_thread_get_type (void);
+RBMtpThread * rb_mtp_thread_new (void);
+
+void rb_mtp_thread_report_errors (RBMtpThread *thread, gboolean use_dialog);
+
+void rb_mtp_thread_open_device (RBMtpThread *thread,
+ LIBMTP_raw_device_t *raw_device,
+ RBMtpOpenCallback func,
+ gpointer data,
+ GDestroyNotify destroy_data);
+void rb_mtp_thread_get_track_list (RBMtpThread *thread,
+ RBMtpTrackListCallback func,
+ gpointer data,
+ GDestroyNotify destroy_data);
+
+void rb_mtp_thread_set_device_name (RBMtpThread *thread, const char *name);
+
+/* albums */
+void rb_mtp_thread_add_to_album (RBMtpThread *thread, LIBMTP_track_t *track, const char *album);
+void rb_mtp_thread_remove_from_album (RBMtpThread *thread, LIBMTP_track_t *track, const char *album);
+void rb_mtp_thread_set_album_image (RBMtpThread *thread, const char *album, GdkPixbuf *image);
+
+/* tracks */
+void rb_mtp_thread_delete_track (RBMtpThread *thread, LIBMTP_track_t *track);
+void rb_mtp_thread_upload_track (RBMtpThread *thread,
+ LIBMTP_track_t *track,
+ const char *filename,
+ RBMtpUploadCallback func,
+ gpointer data,
+ GDestroyNotify destroy_data);
+void rb_mtp_thread_download_track (RBMtpThread *thread,
+ uint32_t track_id,
+ const char *filename,
+ RBMtpDownloadCallback func,
+ gpointer data,
+ GDestroyNotify destroy_data);
+
+#endif
+
diff --git a/po/POTFILES.in b/po/POTFILES.in
index af7e440..7b1a755 100644
--- a/po/POTFILES.in
+++ b/po/POTFILES.in
@@ -112,6 +112,7 @@ plugins/magnatune/magnatune/__init__.py
plugins/mtpdevice/rb-mtp-gst-src.c
plugins/mtpdevice/rb-mtp-plugin.c
plugins/mtpdevice/rb-mtp-source.c
+plugins/mtpdevice/rb-mtp-thread.c
[type: gettext/ini]plugins/power-manager/power-manager.rb-plugin.in
plugins/power-manager/rb-power-manager-plugin.c
plugins/pythonconsole/pythonconsole.py
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]