GSoC: MTP Devices initial release
authorFabio Ritrovato <exsephiroth87@gmail.com>
Mon, 2 Feb 2009 18:06:28 +0000 (19:06 +0100)
committerLaurent Aimar <fenrir@videolan.org>
Wed, 11 Feb 2009 22:28:06 +0000 (23:28 +0100)
I have modified the original author patch to fix:
- vlc_cancelrestore (mtp).
- libmtp initialisation (race condition).

Signed-off-by: Laurent Aimar <fenrir@videolan.org>
configure.ac
modules/access/Modules.am
modules/access/mtp.c [new file with mode: 0644]
modules/services_discovery/Modules.am
modules/services_discovery/mtp.c [new file with mode: 0644]

index b0af3a2..0f2752d 100644 (file)
@@ -918,6 +918,21 @@ then
   )
 fi
 
+dnl Check for mtp
+AC_ARG_ENABLE(mtp,
+  [  --enable-mtp            MTP devices support (default enabled)])
+
+if test "${enable_mtp}" != "no" -a "${SYS}" != "mingw32" -a "${SYS}" != "mingwce"
+then
+  PKG_CHECK_MODULES(MTP, libmtp >= 0.3.0,
+    [ VLC_ADD_PLUGIN([mtp])
+      VLC_ADD_PLUGIN([access_mtp])
+      VLC_ADD_LIBS([mtp access_mtp],[$MTP_LIBS])
+      VLC_ADD_CFLAGS([mtp access_mtp],[$MTP_CFLAGS])],
+       [AC_MSG_WARN(MTP library not found)]
+  )
+fi
+
 dnl Check for dbus
 AC_ARG_ENABLE(dbus,
   [  --enable-dbus           Linux D-BUS message bus system (default enabled)])
index 17076c1..d8fda93 100644 (file)
@@ -40,6 +40,7 @@ SOURCES_cdda = \
 SOURCES_access_jack = jack.c
 SOURCES_access_alsa = alsa.c
 SOURCES_access_oss = oss.c
+SOURCES_access_mtp = mtp.c
 
 libvlc_LTLIBRARIES += \
        libaccess_file_plugin.la \
diff --git a/modules/access/mtp.c b/modules/access/mtp.c
new file mode 100644 (file)
index 0000000..87896ac
--- /dev/null
@@ -0,0 +1,325 @@
+/*****************************************************************************
+ * mtp.c: mtp input (mtp: access plug-in)
+ *****************************************************************************
+ * Copyright (C) 2001-2006 the VideoLAN team
+ * Copyright © 2006-2008 Rémi Denis-Courmont
+ *
+ * Authors: Fabio Ritrovato <exsephiroth87@gmail.com>
+ * Original file.c: Christophe Massiot <massiot@via.ecp.fr>
+ *                  Rémi Denis-Courmont <rem # videolan # 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.
+ *
+ * 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 Street, Fifth Floor, Boston MA 02110-1301, USA.
+ *****************************************************************************/
+
+/*****************************************************************************
+ * Preamble
+ *****************************************************************************/
+#ifdef HAVE_CONFIG_H
+# include "config.h"
+#endif
+
+#include <vlc_common.h>
+#include <vlc_plugin.h>
+#include <vlc_input.h>
+#include <vlc_access.h>
+#include <vlc_interface.h>
+
+#include <assert.h>
+#include <errno.h>
+#ifdef HAVE_SYS_TYPES_H
+#   include <sys/types.h>
+#endif
+#ifdef HAVE_SYS_STAT_H
+#   include <sys/stat.h>
+#endif
+#ifdef HAVE_FCNTL_H
+#   include <fcntl.h>
+#endif
+
+#include <unistd.h>
+#include <poll.h>
+
+#include <vlc_charset.h>
+
+#include "libmtp.h"
+
+/*****************************************************************************
+ * Module descriptor
+ *****************************************************************************/
+
+static int  Open ( vlc_object_t * );
+static void Close( vlc_object_t * );
+
+#define CACHING_TEXT N_("Caching value in ms")
+#define CACHING_LONGTEXT N_( \
+    "Caching value for files. This " \
+    "value should be set in milliseconds." )
+
+vlc_module_begin()
+    set_description( N_("MTP input") )
+    set_shortname( N_("MTP") )
+    set_category( CAT_INPUT )
+    set_subcategory( SUBCAT_INPUT_ACCESS )
+    set_capability( "access", 0 )
+    add_shortcut( "mtp" )
+    set_callbacks( Open, Close )
+vlc_module_end()
+
+/*****************************************************************************
+ * Exported prototypes
+ *****************************************************************************/
+
+static int  Seek( access_t *, int64_t );
+static ssize_t Read( access_t *, uint8_t *, size_t );
+static int  Control( access_t *, int, va_list );
+
+static int  open_file( access_t *, const char * );
+
+struct access_sys_t
+{
+    unsigned int i_nb_reads;
+    int fd;
+};
+
+/*****************************************************************************
+ * Open: open the file
+ *****************************************************************************/
+static int Open( vlc_object_t *p_this )
+{
+    access_t     *p_access = ( access_t* )p_this;
+    access_sys_t *p_sys;
+    uint32_t i_bus;
+    uint8_t i_dev;
+    uint16_t i_product_id;
+    int i_track_id;
+    LIBMTP_raw_device_t *p_rawdevices;
+    LIBMTP_mtpdevice_t *p_device;
+    int i_numrawdevices;
+    int i_ret;
+
+    /* Update default_pts to a suitable value for file access */
+    var_Create( p_access, "file-caching", VLC_VAR_INTEGER|VLC_VAR_DOINHERIT );
+
+    if( sscanf( p_access->psz_path, "%"SCNu32":%"SCNu8":%"SCNu16":%d", &i_bus,
+                &i_dev, &i_product_id, &i_track_id ) != 4 )
+        return VLC_EGENERIC;
+    i_ret = LIBMTP_Detect_Raw_Devices( &p_rawdevices, &i_numrawdevices );
+    if( i_ret != 0 || i_numrawdevices <= 0 || !p_rawdevices )
+        return VLC_EGENERIC;
+
+    for( int i = 0; i < i_numrawdevices; i++ )
+    {
+        if( i_bus == p_rawdevices[i].bus_location &&
+            i_dev == p_rawdevices[i].devnum &&
+            i_product_id == p_rawdevices[i].device_entry.product_id )
+        {
+            if( ( p_device = LIBMTP_Open_Raw_Device( &p_rawdevices[i] )
+                ) != NULL )
+            {
+                free( p_access->psz_path );
+                if( ( p_access->psz_path = tempnam( NULL, "vlc" ) ) == NULL )
+                {
+                    LIBMTP_Release_Device( p_device );
+                    free( p_rawdevices );
+                    return VLC_ENOMEM;
+                }
+                else
+                {
+                    msg_Dbg( p_access, "About to write %s", p_access->psz_path );
+                    LIBMTP_Get_File_To_File( p_device, i_track_id,
+                                             p_access->psz_path, NULL, NULL );
+                    LIBMTP_Release_Device( p_device );
+                    i = i_numrawdevices;
+                }
+            }
+            else
+            {
+                free( p_rawdevices );
+                return VLC_EGENERIC;
+            }
+        }
+    }
+    free( p_rawdevices );
+
+    STANDARD_READ_ACCESS_INIT;
+    p_sys->i_nb_reads = 0;
+    int fd = p_sys->fd = -1;
+
+    /* Open file */
+    msg_Dbg( p_access, "opening file `%s'", p_access->psz_path );
+    fd = open_file( p_access, p_access->psz_path );
+
+    if( fd == -1 )
+    {
+        free( p_sys );
+        return VLC_EGENERIC;
+    }
+    p_sys->fd = fd;
+
+#ifdef HAVE_SYS_STAT_H
+    struct stat st;
+    if( fstat( fd, &st ) )
+        msg_Err( p_access, "fstat(%d): %m", fd );
+    p_access->info.i_size = st.st_size;
+#else
+# warning File size not known!
+#endif
+
+    return VLC_SUCCESS;
+}
+
+/*****************************************************************************
+ * Close: close the target
+ *****************************************************************************/
+static void Close( vlc_object_t * p_this )
+{
+    access_t     *p_access = ( access_t* )p_this;
+    access_sys_t *p_sys = p_access->p_sys;
+
+    close ( p_sys->fd );
+    if(        utf8_unlink( p_access->psz_path ) != 0 )
+        msg_Err( p_access, "Error deleting file %s, %m", p_access->psz_path );
+    free( p_sys );
+}
+
+/*****************************************************************************
+ * Read: standard read on a file descriptor.
+ *****************************************************************************/
+static ssize_t Read( access_t *p_access, uint8_t *p_buffer, size_t i_len )
+{
+    access_sys_t *p_sys = p_access->p_sys;
+    ssize_t i_ret;
+    int fd = p_sys->fd;
+
+    i_ret = read( fd, p_buffer, i_len );
+
+    if( i_ret < 0 )
+    {
+        switch( errno )
+        {
+            case EINTR:
+            case EAGAIN:
+                break;
+
+            default:
+                msg_Err( p_access, "read failed (%m)" );
+                intf_UserFatal( p_access, false, _( "File reading failed" ),
+                                _( "VLC could not read the file." ) );
+                p_access->info.b_eof = true;
+                return 0;
+        }
+    }
+    else if( i_ret > 0 )
+        p_access->info.i_pos += i_ret;
+    else
+        p_access->info.b_eof = true;
+
+    p_sys->i_nb_reads++;
+
+    return i_ret;
+}
+
+
+/*****************************************************************************
+ * Seek: seek to a specific location in a file
+ *****************************************************************************/
+static int Seek( access_t *p_access, int64_t i_pos )
+{
+    p_access->info.i_pos = i_pos;
+    p_access->info.b_eof = false;
+
+    lseek( p_access->p_sys->fd, i_pos, SEEK_SET );
+    return VLC_SUCCESS;
+}
+
+/*****************************************************************************
+ * Control:
+ *****************************************************************************/
+static int Control( access_t *p_access, int i_query, va_list args )
+{
+    bool   *pb_bool;
+    int64_t      *pi_64;
+
+    switch( i_query )
+    {
+        /* */
+        case ACCESS_CAN_SEEK:
+        case ACCESS_CAN_FASTSEEK:
+            pb_bool = ( bool* )va_arg( args, bool* );
+            *pb_bool = true;
+            break;
+
+        case ACCESS_CAN_PAUSE:
+        case ACCESS_CAN_CONTROL_PACE:
+            pb_bool = ( bool* )va_arg( args, bool* );
+            *pb_bool = true;
+            break;
+
+        case ACCESS_GET_PTS_DELAY:
+            pi_64 = ( int64_t* )va_arg( args, int64_t * );
+            *pi_64 = var_GetInteger( p_access, "file-caching" ) * INT64_C( 1000 );
+            break;
+
+        /* */
+        case ACCESS_SET_PAUSE_STATE:
+            /* Nothing to do */
+            break;
+
+        case ACCESS_GET_TITLE_INFO:
+        case ACCESS_SET_TITLE:
+        case ACCESS_SET_SEEKPOINT:
+        case ACCESS_SET_PRIVATE_ID_STATE:
+        case ACCESS_GET_META:
+        case ACCESS_GET_PRIVATE_ID_STATE:
+        case ACCESS_GET_CONTENT_TYPE:
+            return VLC_EGENERIC;
+
+        default:
+            msg_Warn( p_access, "unimplemented query %d in control", i_query );
+            return VLC_EGENERIC;
+
+    }
+    return VLC_SUCCESS;
+}
+
+/*****************************************************************************
+ * open_file: Opens a specific file
+ *****************************************************************************/
+static int open_file( access_t *p_access, const char *path )
+{
+    int fd = utf8_open( path, O_RDONLY | O_NONBLOCK /* O_LARGEFILE*/, 0666 );
+    if( fd == -1 )
+    {
+        msg_Err( p_access, "cannot open file %s (%m)", path );
+        intf_UserFatal( p_access, false, _( "File reading failed" ),
+                        _( "VLC could not open the file \"%s\"." ), path );
+        return -1;
+    }
+
+#if defined( HAVE_FCNTL )
+    fcntl( fd, F_SETFD, fcntl( fd, F_GETFD ) | FD_CLOEXEC );
+
+    /* We'd rather use any available memory for reading ahead
+     * than for caching what we've already seen/heard */
+# if defined( F_RDAHEAD )
+    fcntl( fd, F_RDAHEAD, 1 );
+# endif
+# if defined( F_NOCACHE )
+    fcntl( fd, F_NOCACHE, 1 );
+# endif
+#endif
+
+    return fd;
+}
index 7aa24b8..055500e 100644 (file)
@@ -5,3 +5,4 @@ SOURCES_upnp_cc = upnp_cc.cpp
 SOURCES_upnp_intel = upnp_intel.cpp upnp_intel.hpp
 SOURCES_bonjour = bonjour.c
 SOURCES_podcast = podcast.c
+SOURCES_mtp = mtp.c
diff --git a/modules/services_discovery/mtp.c b/modules/services_discovery/mtp.c
new file mode 100644 (file)
index 0000000..55c104e
--- /dev/null
@@ -0,0 +1,289 @@
+/*****************************************************************************
+ * mtp.c :  MTP interface module
+ *****************************************************************************
+ * Copyright (C) 2009 the VideoLAN team
+ *
+ * Authors: Fabio Ritrovato <exsephiroth87@gmail.com>
+ *
+ * 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.
+ *
+ * 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 Street, Fifth Floor, Boston MA 02110-1301, USA.
+ *****************************************************************************/
+
+#ifdef HAVE_CONFIG_H
+# include "config.h"
+#endif
+
+#include <vlc_common.h>
+#include <vlc_playlist.h>
+#include <vlc_plugin.h>
+#include <errno.h>
+#include <vlc_charset.h>
+#include <vlc_interface.h>
+#include <vlc_services_discovery.h>
+
+#ifdef HAVE_SYS_STAT_H
+#include <sys/stat.h>
+#endif
+
+#include "libmtp.h"
+
+/*****************************************************************************
+ * Module descriptor
+ *****************************************************************************/
+static int Open( vlc_object_t * );
+static void Close( vlc_object_t * );
+
+vlc_module_begin()
+    set_shortname( "MTP" )
+    set_description( N_( "MTP devices" ) )
+    set_category( CAT_PLAYLIST )
+    set_subcategory( SUBCAT_PLAYLIST_SD )
+    set_capability( "services_discovery", 0 )
+    set_callbacks( Open, Close )
+    linked_with_a_crap_library_which_uses_atexit()
+vlc_module_end()
+
+
+/*****************************************************************************
+ * Local prototypes
+ *****************************************************************************/
+
+static void *Run( void * );
+
+static int AddDevice( services_discovery_t *, LIBMTP_raw_device_t * );
+static void AddTrack( services_discovery_t *, LIBMTP_track_t *);
+static void CloseDevice( services_discovery_t * );
+static int CountTracks( uint64_t const, uint64_t const, void const * const );
+
+/*****************************************************************************
+ * Local structures
+ *****************************************************************************/
+
+struct services_discovery_sys_t
+{
+    int i_tracks_num;
+    input_item_t **pp_items;
+    int i_count;
+    char *psz_name;
+    uint32_t i_bus;
+    uint8_t i_dev;
+    uint16_t i_product_id;
+    vlc_thread_t thread;
+};
+
+static vlc_mutex_t mtp_lock = VLC_STATIC_MUTEX;
+static bool b_mtp_initialized = false;
+
+/*****************************************************************************
+ * Open: initialize and create stuff
+ *****************************************************************************/
+static int Open( vlc_object_t *p_this )
+{
+    services_discovery_t *p_sd = ( services_discovery_t * )p_this;
+    services_discovery_sys_t *p_sys;
+
+    if( !( p_sys = malloc( sizeof( services_discovery_sys_t ) ) ) )
+        return VLC_ENOMEM;
+    p_sd->p_sys = p_sys;
+    p_sys->psz_name = NULL;
+
+    vlc_mutex_lock( &mtp_lock );
+    if( !b_mtp_initialized )
+    {
+        LIBMTP_Init();
+        b_mtp_initialized = true;
+    }
+    vlc_mutex_unlock( &mtp_lock );
+
+    if (vlc_clone (&p_sys->thread, Run, p_sd, VLC_THREAD_PRIORITY_LOW))
+    {
+        free (p_sys);
+        return VLC_EGENERIC;
+    }
+    return VLC_SUCCESS;
+}
+
+/*****************************************************************************
+ * Close: cleanup
+ *****************************************************************************/
+static void Close( vlc_object_t *p_this )
+{
+    services_discovery_t *p_sd = ( services_discovery_t * )p_this;
+
+    if( p_sd->p_sys->psz_name != NULL )
+        free( p_sd->p_sys->psz_name );
+    vlc_cancel (p_sd->p_sys->thread);
+    vlc_join (p_sd->p_sys->thread, NULL);
+    free( p_sd->p_sys );
+}
+
+/*****************************************************************************
+ * Run: main thread
+ *****************************************************************************/
+static void *Run( void *data )
+{
+    LIBMTP_raw_device_t *p_rawdevices;
+    int i_numrawdevices;
+    int i_ret;
+    int i_status = 0;
+    services_discovery_t *p_sd = data;
+
+    for(;;)
+    {
+        int canc = vlc_savecancel();
+        i_ret = LIBMTP_Detect_Raw_Devices( &p_rawdevices, &i_numrawdevices );
+        if ( i_ret == 0 && i_numrawdevices > 0 && p_rawdevices != NULL &&
+             i_status == 0 )
+        {
+            /* Found a new device, add it */
+            msg_Info( p_sd, "New device found" );
+            if( AddDevice( p_sd, &p_rawdevices[0] ) == VLC_SUCCESS )
+                i_status = 1;
+        }
+        else
+        {
+            if ( ( i_ret != 0 || i_numrawdevices == 0 || p_rawdevices == NULL )
+                 && i_status == 1)
+            {
+                /* The device is not connected anymore, delete it */
+                msg_Info( p_sd, "Device disconnected" );
+                CloseDevice( p_sd );
+                i_status = 0;
+            }
+        }
+        free( p_rawdevices );
+        vlc_restorecancel(canc);
+        msleep( 500000 );
+    }
+    return NULL;
+}
+
+/*****************************************************************************
+ * Everything else
+ *****************************************************************************/
+static int AddDevice( services_discovery_t *p_sd,
+                      LIBMTP_raw_device_t *p_raw_device )
+{
+    char *psz_name = NULL;
+    LIBMTP_mtpdevice_t *p_device;
+    LIBMTP_track_t *p_track, *p_tmp;
+
+    if( ( p_device = LIBMTP_Open_Raw_Device( p_raw_device ) ) != NULL )
+    {
+        if( !( psz_name = LIBMTP_Get_Friendlyname( p_device ) ) )
+            if( !( psz_name = LIBMTP_Get_Modelname( p_device ) ) )
+                if( !( psz_name = strdup( N_( "MTP Device" ) ) ) )
+                    return VLC_ENOMEM;
+        msg_Info( p_sd, "Found device: %s", psz_name );
+        p_sd->p_sys->i_bus = p_raw_device->bus_location;
+        p_sd->p_sys->i_dev = p_raw_device->devnum;
+        p_sd->p_sys->i_product_id = p_raw_device->device_entry.product_id;
+        if( ( p_track = LIBMTP_Get_Tracklisting_With_Callback( p_device,
+                            CountTracks, p_sd ) ) == NULL )
+            msg_Info( p_sd, "No tracks on the device" );
+        else
+        {
+            if( !( p_sd->p_sys->pp_items = calloc( p_sd->p_sys->i_tracks_num,
+                                                   sizeof( input_item_t * ) ) ) )
+                return VLC_ENOMEM;
+            p_sd->p_sys->i_count = 0;
+            while( p_track != NULL )
+            {
+                msg_Dbg( p_sd, "Track found: %s - %s", p_track->artist,
+                         p_track->title );
+                AddTrack( p_sd, p_track );
+                p_tmp = p_track;
+                p_track = p_track->next;
+                LIBMTP_destroy_track_t( p_tmp );
+            }
+        }
+        p_sd->p_sys->psz_name = psz_name;
+        LIBMTP_Release_Device( p_device );
+        return VLC_SUCCESS;
+    }
+    else
+    {
+        msg_Info( p_sd, "No device found, after all" );
+        return VLC_EGENERIC;
+    }
+}
+
+static void AddTrack( services_discovery_t *p_sd, LIBMTP_track_t *p_track )
+{
+    input_item_t *p_input;
+    char *psz_string;
+    char *extension;
+
+    extension = rindex( p_track->filename, '.' );
+    if( asprintf( &psz_string, "mtp://%"PRIu32":%"PRIu8":%"PRIu16":%d%s",
+                  p_sd->p_sys->i_bus, p_sd->p_sys->i_dev,
+                  p_sd->p_sys->i_product_id, p_track->item_id,
+                  extension ) == -1 )
+    {
+        msg_Err( p_sd, "Error adding %s, skipping it", p_track->filename );
+        return;
+    }
+    if( ( p_input = input_item_New( p_sd, psz_string,
+                                    p_track->title ) ) == NULL )
+    {
+        msg_Err( p_sd, "Error adding %s, skipping it", p_track->filename );
+        return;
+    }
+    input_item_SetArtist( p_input, p_track->artist );
+    input_item_SetGenre( p_input, p_track->genre );
+    input_item_SetAlbum( p_input, p_track->album );
+    free( psz_string );
+    if( asprintf( &psz_string, "%d", p_track->tracknumber ) != -1 )
+    {
+        input_item_SetTrackNum( p_input, psz_string );
+        free( psz_string );
+    }
+    if( asprintf( &psz_string, "%d", p_track->rating ) != -1 )
+    {
+        input_item_SetRating( p_input, psz_string );
+        free( psz_string );
+    }
+    input_item_SetDate( p_input, p_track->date );
+    input_item_SetDuration( p_input, p_track->duration * 1000 );
+    services_discovery_AddItem( p_sd, p_input, NULL );
+    p_sd->p_sys->pp_items[p_sd->p_sys->i_count++] = p_input;
+}
+
+static void CloseDevice( services_discovery_t *p_sd )
+{
+    input_item_t **pp_items = p_sd->p_sys->pp_items;
+    int i_i;
+
+    if( pp_items != NULL )
+    {
+        for( i_i = 0; i_i < p_sd->p_sys->i_count; i_i++ )
+        {
+            if( pp_items[i_i] != NULL )
+            {
+                services_discovery_RemoveItem( p_sd, pp_items[i_i] );
+                vlc_gc_decref( pp_items[i_i] );
+            }
+        }
+        free( pp_items );
+    }
+}
+
+static int CountTracks( uint64_t const sent, uint64_t const total,
+                        void const * const data )
+{
+    VLC_UNUSED( sent );
+    services_discovery_t *p_sd = (services_discovery_t *)data;
+    p_sd->p_sys->i_tracks_num = total;
+    return 0;
+}