From 462b4cf3fb94fb35628d2d4f3440834808af2e95 Mon Sep 17 00:00:00 2001
From: Fabio Ritrovato <exsephiroth87@gmail.com>
Date: Wed, 3 Sep 2008 14:24:48 +0200
Subject: [PATCH] MTP: access, services discovery and meta writer modules

---
 configure.ac                          |   16 +
 modules/access/Modules.am             |    1 +
 modules/access/mtp.c                  |  453 ++++++++++++++
 modules/services_discovery/Modules.am |    1 +
 modules/services_discovery/mtp.c      | 1081 +++++++++++++++++++++++++++++++++
 5 files changed, 1552 insertions(+), 0 deletions(-)
 create mode 100644 modules/access/mtp.c
 create mode 100644 modules/services_discovery/mtp.c

diff --git a/configure.ac b/configure.ac
index 0aeb086..63dd584 100644
--- a/configure.ac
+++ b/configure.ac
@@ -882,6 +882,22 @@ 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,
+    [ AC_DEFINE( HAVE_MTP, 1, [Define if you have the MTP library])
+      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)])
diff --git a/modules/access/Modules.am b/modules/access/Modules.am
index c91bfd0..9f51763 100644
--- a/modules/access/Modules.am
+++ b/modules/access/Modules.am
@@ -37,6 +37,7 @@ SOURCES_cdda = \
         vcd/cdrom_internals.h \
         $(NULL)
 SOURCES_access_jack = jack.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
index 0000000..9e595dd
--- /dev/null
+++ b/modules/access/mtp.c
@@ -0,0 +1,453 @@
+/*****************************************************************************
+ * mtp.c: mtp input (mtp: access plug-in)
+ *****************************************************************************
+ * Copyright (C) 2001-2006 the VideoLAN team
+ * Copyright © 2006-2007 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
+
+#if defined( WIN32 ) && !defined( UNDER_CE )
+#   include <io.h>
+#   include <ctype.h>
+#else
+#   include <unistd.h>
+#   include <poll.h>
+#endif
+
+#if defined( WIN32 ) && !defined( UNDER_CE )
+#   ifdef lseek
+#      undef lseek
+#   endif
+#   define lseek _lseeki64
+#elif defined( UNDER_CE )
+#   ifdef read
+#      undef read
+#   endif
+#   define read(a,b,c) fread(b,1,c,a)
+#   define close(a) fclose(a)
+#   ifdef lseek
+#      undef lseek
+#   endif
+#   define lseek fseek
+#endif
+
+#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 );
+    add_integer( "mtp-caching", DEFAULT_PTS_DELAY / 1000, NULL, CACHING_TEXT, CACHING_LONGTEXT, true );
+    set_capability( "access", 50 );
+    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;
+    bool   b_kfir;
+
+    int fd;
+
+    /* */
+    bool b_seekable;
+    bool b_pace_control;
+};
+
+/*****************************************************************************
+ * 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;
+    int i_bus;
+    int i_dev;
+    int i_product_id;
+    int i_track_id;
+    LIBMTP_raw_device_t *p_rawdevices;
+    LIBMTP_mtpdevice_t *p_device;
+    int i_numrawdevices;
+    int i_ret;
+    char *psz_to_path;
+    vlc_mutex_t *lock;
+    char *psz_lock_name;
+
+    /* Update default_pts to a suitable value for file access */
+    var_Create( p_access, "file-caching", VLC_VAR_INTEGER | VLC_VAR_DOINHERIT );
+
+    STANDARD_READ_ACCESS_INIT;
+    p_sys->i_nb_reads = 0;
+    p_sys->b_kfir = false;
+    int fd = p_sys->fd = -1;
+
+    p_sys->b_seekable = true;
+    p_sys->b_pace_control = true;
+
+    sscanf( p_access->psz_path, "%d:%d:%d:%d", &i_bus, &i_dev, &i_product_id, &i_track_id );
+    if( ( asprintf( &psz_lock_name, "mtp-%d%d%d", i_bus, i_dev, i_product_id ) ) == -1 )
+    {
+        free( p_sys );
+        return VLC_ENOMEM;
+    }
+    i_ret = LIBMTP_Detect_Raw_Devices( &p_rawdevices, &i_numrawdevices );
+    if ( i_ret == 0 && i_numrawdevices > 0 && p_rawdevices != NULL )
+    {
+        for( i_ret = 0; i_ret < i_numrawdevices; i_ret++ )
+        {
+            if( i_bus == (int)p_rawdevices[i_ret].bus_location && i_dev == (int)p_rawdevices[i_ret].devnum && i_product_id == (int)p_rawdevices[i_ret].device_entry.product_id )
+            {
+                lock = var_AcquireMutex( psz_lock_name );
+                free( psz_lock_name );
+                if( ( p_device = LIBMTP_Open_Raw_Device( &p_rawdevices[i_ret] ) ) != NULL )
+                {
+                    if( asprintf( &psz_to_path, "%s/mtp%d", config_GetCacheDir(), i_track_id ) == -1 )
+                    {
+                        LIBMTP_Release_Device( p_device );
+                        vlc_mutex_unlock( lock );
+                        free( p_sys );
+                        free( p_rawdevices );
+                        return VLC_ENOMEM;
+                    }
+                    else
+                    {
+                        msg_Dbg( p_access, "About to write %s...", psz_to_path );
+                        LIBMTP_Get_File_To_File( p_device, i_track_id, psz_to_path, NULL, NULL );
+                        p_access->psz_path = psz_to_path;
+                        LIBMTP_Release_Device( p_device );
+                        vlc_mutex_unlock( lock );
+                        i_ret = i_numrawdevices;
+                    }
+                }
+                else
+                {
+                    vlc_mutex_unlock( lock );
+                    free( p_sys );
+                    free( p_rawdevices );
+                    return VLC_EGENERIC;
+                }
+            }
+        }
+        free( p_rawdevices );
+    }
+
+    /* 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;
+    if (!S_ISREG (st.st_mode)
+     && !S_ISBLK (st.st_mode)
+     && !S_ISCHR (st.st_mode))
+        p_sys->b_seekable = false;
+#else
+    p_sys->b_seekable = true;
+# 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;
+
+#if !defined(WIN32) && !defined(UNDER_CE)
+    if( !p_sys->b_pace_control )
+    {
+        if( !p_sys->b_kfir )
+        {
+            /* Find if some data is available. This won't work under Windows. */
+            do
+            {
+                struct pollfd ufd;
+
+                if( p_access->b_die )
+                    return 0;
+
+                memset (&ufd, 0, sizeof (ufd));
+                ufd.fd = fd;
+                ufd.events = POLLIN;
+
+                i_ret = poll (&ufd, 1, 500);
+            }
+            while (i_ret <= 0);
+
+            i_ret = read (fd, p_buffer, i_len);
+        }
+        else
+        {
+            /* b_kfir ; work around a buggy poll() driver implementation */
+            while (((i_ret = read (fd, p_buffer, i_len)) == 0)
+                && !p_access->b_die)
+            {
+                msleep( INPUT_ERROR_SLEEP );
+            }
+        }
+    }
+    else
+#endif /* WIN32 || UNDER_CE */
+        /* b_pace_control || WIN32 */
+        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."));
+        }
+
+        /* Delay a bit to avoid consuming all the CPU. This is particularly
+         * useful when reading from an unconnected FIFO. */
+        msleep( INPUT_ERROR_SLEEP );
+    }
+
+    p_sys->i_nb_reads++;
+
+#ifdef HAVE_SYS_STAT_H
+    if( p_access->info.i_size != 0 &&
+        (p_sys->i_nb_reads % INPUT_FSTAT_NB_READS) == 0 )
+    {
+        struct stat st;
+
+        if ((fstat (fd, &st) == 0)
+         && (p_access->info.i_size != st.st_size))
+        {
+            p_access->info.i_size = st.st_size;
+            p_access->info.i_update |= INPUT_UPDATE_SIZE;
+        }
+    }
+#endif
+
+    if( i_ret > 0 )
+        p_access->info.i_pos += i_ret;
+    else if( i_ret == 0 )
+        p_access->info.b_eof = true;
+
+    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 )
+{
+    access_sys_t *p_sys = p_access->p_sys;
+    bool   *pb_bool;
+    int          *pi_int;
+    int64_t      *pi_64;
+
+    switch( i_query )
+    {
+        /* */
+        case ACCESS_CAN_SEEK:
+        case ACCESS_CAN_FASTSEEK:
+            pb_bool = (bool*)va_arg( args, bool* );
+            *pb_bool = p_sys->b_seekable;
+            break;
+
+        case ACCESS_CAN_PAUSE:
+        case ACCESS_CAN_CONTROL_PACE:
+            pb_bool = (bool*)va_arg( args, bool* );
+            *pb_bool = p_sys->b_pace_control;
+            break;
+
+        /* */
+        case ACCESS_GET_MTU:
+            pi_int = (int*)va_arg( args, int * );
+            *pi_int = 0;
+            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)
+{
+#if defined(WIN32)
+    if (!strcasecmp (p_access->psz_access, "file")
+      && ('/' == path[0]) && isalpha (path[1])
+      && (':' == path[2]) && ('/' == path[3]))
+        /* Explorer can open path such as file:/C:/ or file:///C:/
+         * hence remove leading / if found */
+        path++;
+#endif
+
+#ifdef UNDER_CE
+    p_sys->fd = utf8_fopen( path, "rb" );
+    if ( !p_sys->fd )
+    {
+        msg_Err( p_access, "cannot open file %s", path );
+        intf_UserFatal( p_access, false, _("File reading failed"),
+                        _("VLC could not open the file \"%s\"."), path );
+        return VLC_EGENERIC;
+    }
+
+    fseek( p_sys->fd, 0, SEEK_END );
+    p_access->info.i_size = ftell( p_sys->fd );
+    p_access->info.i_update |= INPUT_UPDATE_SIZE;
+    fseek( p_sys->fd, 0, SEEK_SET );
+#else
+    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_H) && defined(F_FDAHEAD) && defined(F_NOCACHE)
+    /* We'd rather use any available memory for reading ahead
+     * than for caching what we've already seen/heard */
+    fcntl (fd, F_RDAHEAD, 1);
+    fcntl (fd, F_NOCACHE, 1);
+# endif
+#endif
+
+    return fd;
+}
diff --git a/modules/services_discovery/Modules.am b/modules/services_discovery/Modules.am
index c489b91..6c96078 100644
--- a/modules/services_discovery/Modules.am
+++ b/modules/services_discovery/Modules.am
@@ -5,3 +5,4 @@ SOURCES_upnp_cc = upnp_cc.cpp
 SOURCES_upnp_intel = upnp_intel.cpp
 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
index 0000000..d2b0042
--- /dev/null
+++ b/modules/services_discovery/mtp.c
@@ -0,0 +1,1081 @@
+/*****************************************************************************
+ * mtp.c :  MTP interface module
+ *****************************************************************************
+ * Copyright (C) 2008 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>
+
+#ifdef HAVE_SYS_STAT_H
+#include <sys/stat.h>
+#endif
+
+#include <libmtp.h>
+
+#define MTP_FILE_ADD 1
+#define MTP_FILE_SAVE 2
+#define MTP_FILE_DELETE 3
+
+/*****************************************************************************
+ * Local prototypes
+ *****************************************************************************/
+
+static void Run( services_discovery_t *p_intf );
+static int Open( vlc_object_t * );
+static void Close( vlc_object_t * );
+static int  WriteMeta( vlc_object_t * );
+static int ItemPutCallback( services_discovery_t *, const char * );
+static int ItemPut( services_discovery_t *, char *, bool, int * );
+static int ItemGetCallback( services_discovery_t *, const char *, int );
+static int ItemGet( services_discovery_t *, char *, int );
+static int ItemDeleteCallback( services_discovery_t *, int );
+static int ItemDelete( services_discovery_t *, int, bool );
+
+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 );
+static void FixName( services_discovery_t *, char * );
+static void SetSDId( services_discovery_t * );
+LIBMTP_filetype_t FindFiletype ( const char * );
+static int ProgressUpdate( uint64_t const, uint64_t const, void const * const );
+
+services_discovery_t *p_sd_global;
+
+/*****************************************************************************
+ * Local structures
+ *****************************************************************************/
+
+typedef struct mtp_queue_t
+{
+    char *psz_string;
+    int i_int;
+    int i_action;
+    struct mtp_queue_t *p_next;
+}mtp_queue_t;
+
+typedef struct
+{
+    VLC_COMMON_MEMBERS
+    vlc_mutex_t lock;
+    mtp_queue_t *p_first;
+    mtp_queue_t *p_last;
+    int i_success;
+    char *psz_lock_name;
+}mtp_thread_t;
+
+struct services_discovery_sys_t
+{
+    LIBMTP_mtpdevice_t *p_device;
+    int i_tracks_num;
+    int *pi_id_list;
+    int i_count;
+    char *psz_name;
+    uint32_t i_bus;
+    uint8_t i_dev;
+    uint16_t i_product_id;
+    int i_id;
+    int i_progress;
+    int i_current;
+    int i_total;
+    mtp_thread_t *p_sdt;
+};
+
+static void FileActionThread( mtp_thread_t * );
+
+/*****************************************************************************
+ * Module descriptor
+ *****************************************************************************/
+
+vlc_module_begin();
+    set_shortname( "MTP" );
+    set_description( N_( "MTP devices detection" ) );
+    set_category( CAT_PLAYLIST );
+    set_subcategory( SUBCAT_PLAYLIST_SD );
+    set_capability( "services_discovery", 0 );
+    set_callbacks( Open, Close );
+    add_submodule();
+        set_capability( "meta writer", 75 );
+        set_callbacks( WriteMeta, NULL );
+vlc_module_end();
+
+/*****************************************************************************
+ * 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->pf_run = Run;
+    p_sd->pf_item_put = ItemPutCallback;
+    p_sd->pf_item_get = ItemGetCallback;
+    p_sd->pf_item_delete = ItemDeleteCallback;
+    p_sd->p_sys = p_sys;
+    p_sd_global = p_sd;
+    p_sys->p_device = NULL;
+    p_sys->psz_name = NULL;
+    p_sys->i_progress = -1;
+    LIBMTP_Init();
+    services_discovery_SetLocalizedName( p_sd, N_( "MTP Device" ) );
+    if( !( p_sys->p_sdt = vlc_object_create( p_sd->p_libvlc, sizeof( mtp_thread_t ) ) ) )
+    {
+        free( p_sys );
+        return VLC_ENOMEM;
+    }
+    p_sys->p_sdt->p_first = NULL;
+    p_sys->p_sdt->p_last = NULL;
+    vlc_mutex_init( &p_sys->p_sdt->lock );
+    p_sys->p_sdt->b_die = false;
+    vlc_thread_create( p_sys->p_sdt, "mtp file action", FileActionThread, VLC_THREAD_PRIORITY_LOW, false );
+    return VLC_SUCCESS;
+}
+
+/*****************************************************************************
+ * Close: cleanup
+ *****************************************************************************/
+static void Close( vlc_object_t *p_this )
+{
+    services_discovery_t *p_sd = ( services_discovery_t * )p_this;
+
+    p_sd->p_sys->p_sdt->b_die = true;
+    vlc_object_kill( p_sd->p_sys->p_sdt );
+    vlc_mutex_destroy( &p_sd->p_sys->p_sdt->lock );
+    vlc_thread_join( p_sd->p_sys->p_sdt );
+    vlc_object_release( p_sd->p_sys->p_sdt );
+    if( p_sd->p_sys->p_device != NULL )
+        LIBMTP_Release_Device( p_sd->p_sys->p_device );
+    if( p_sd->p_sys->p_device != NULL )
+        free( p_sd->p_sys->psz_name );
+    free( p_sd->p_sys );
+}
+
+/*****************************************************************************
+ * Run: main thread
+ *****************************************************************************/
+static void Run( services_discovery_t *p_sd )
+{
+    LIBMTP_raw_device_t *p_rawdevices;
+    int i_numrawdevices;
+    int i_ret;
+    int i_status = 0;
+
+    SetSDId( p_sd );
+    while( !p_sd->b_die )
+    {
+        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 );
+                FixName( p_sd, ( char * )N_( "MTP Device" ) );
+                i_status = 0;
+            }
+        }
+        free( p_rawdevices );
+        msleep( 5000 );
+    }
+}
+
+/*****************************************************************************
+ * WriteMeta
+ *****************************************************************************/
+static int WriteMeta( vlc_object_t *p_this )
+{
+    int i_bus;
+    int i_dev;
+    int i_product_id;
+    int i_new_id;
+    vlc_mutex_t *lock;
+    char *psz_lock_name;
+    char *psz_to_path;
+    char *psz_extension;
+    char *psz_temp;
+
+    meta_export_t *p_object = (meta_export_t *)p_this;
+    input_item_t *p_item = p_object->p_item;
+    vlc_mutex_lock( &p_item->lock );
+    if( strncmp( p_item->psz_uri, "mtp://", 6 ) )
+    {
+        vlc_mutex_unlock( &p_item->lock );
+        return VLC_EGENERIC;
+    }
+    sscanf( p_item->psz_uri, "mtp://%d:%d:%d:%*d", &i_bus, &i_dev, &i_product_id );
+    if( i_bus != (int)p_sd_global->p_sys->i_bus || i_dev != (int)p_sd_global->p_sys->i_dev || i_product_id != (int)p_sd_global->p_sys->i_product_id )
+    {
+        vlc_mutex_unlock( &p_item->lock );
+        return VLC_EGENERIC;
+    }
+    psz_extension = rindex( p_item->psz_uri, '.' );
+    if( ( asprintf( &psz_lock_name, "mtp-%d%d%d", i_bus, i_dev, i_product_id ) ) == -1 )
+    {
+        vlc_mutex_unlock( &p_item->lock );
+        return VLC_ENOMEM;
+    }
+    if( asprintf( &psz_to_path, "%s/%d%s", config_GetCacheDir(), p_item->i_id, psz_extension ) == -1 )
+    {
+        free( psz_lock_name );
+        vlc_mutex_unlock( &p_item->lock );
+        return VLC_ENOMEM;
+    }
+    lock = var_AcquireMutex( psz_lock_name );
+    if( ItemGet( p_sd_global, psz_to_path, p_item->i_id ) != VLC_SUCCESS )
+    {
+        vlc_mutex_unlock( lock );
+        free( psz_to_path );
+        free( psz_lock_name );
+        msg_Err( p_object, "Error updating metadata..." );
+        vlc_mutex_unlock( &p_item->lock );
+        return VLC_EGENERIC;
+    }
+    psz_temp = p_item->psz_uri;
+    p_item->psz_uri = psz_to_path;
+    vlc_mutex_unlock( &p_item->lock );
+    module_t *p_mod = module_Need( p_object, "meta writer", NULL, 0 );
+    if( p_mod )
+        module_Unneed( p_object, p_mod );
+    input_Preparse( p_object, p_item );
+    vlc_mutex_lock( &p_item->lock );
+    p_item->psz_uri = psz_temp;
+    if( ItemPut( p_sd_global, psz_to_path, false, &i_new_id ) != VLC_SUCCESS )
+    {
+        vlc_mutex_unlock( lock );
+        unlink( psz_to_path );
+        free( psz_to_path );
+        free( psz_lock_name );
+        msg_Err( p_object, "Error updating metadata..." );
+        vlc_mutex_unlock( &p_item->lock );
+        return VLC_EGENERIC;
+    }
+
+    if( ItemDelete( p_sd_global, p_item->i_id, false ) != VLC_SUCCESS )
+    {
+        vlc_mutex_unlock( lock );
+        unlink( psz_to_path );
+        free( psz_to_path );
+        free( psz_lock_name );
+        msg_Err( p_object, "Error updating metadata..." );
+        vlc_mutex_unlock( &p_item->lock );
+        return VLC_EGENERIC;
+    }
+    vlc_mutex_unlock( lock );
+    free( p_item->psz_uri );
+    if( asprintf( &p_item->psz_uri, "mtp://%d:%d:%d:%d%s", i_bus, i_dev, i_product_id, i_new_id, psz_extension ) == -1 )
+        msg_Err( p_object, "Error updating playlist item..." );
+    else
+        msg_Info( p_object, "Metadata updated..." );
+    unlink( psz_to_path );
+    free( psz_to_path );
+    free( psz_lock_name );
+    vlc_mutex_unlock( &p_item->lock );
+    return VLC_SUCCESS;
+}
+
+/*****************************************************************************
+ * ItemPut: file addition callback
+ *****************************************************************************/
+static int ItemPutCallback( services_discovery_t *p_sd, const char *psz_file )
+{
+    mtp_queue_t *p_queue;
+    char *psz_status;
+
+    if( !( p_queue = malloc( sizeof( mtp_queue_t ) ) ) )
+        return VLC_ENOMEM;
+    if( !( strncmp( psz_file, "file://", 7 ) ) )
+        p_queue->psz_string = strdup( psz_file + 7 );
+    else
+        p_queue->psz_string = strdup( psz_file );
+    if( !p_queue->psz_string )
+    {
+        free( p_queue );
+        return VLC_ENOMEM;
+    }
+    p_queue->i_action = MTP_FILE_ADD;
+    p_queue->p_next = NULL;
+    vlc_mutex_lock( &p_sd->p_sys->p_sdt->lock );
+    if( p_sd->p_sys->p_sdt->p_last )
+    {
+        p_sd->p_sys->p_sdt->p_last->p_next = p_queue;
+        p_sd->p_sys->p_sdt->p_last = p_queue;
+        p_sd->p_sys->i_total++;
+    }
+    else
+    {
+        p_sd->p_sys->p_sdt->p_first = p_queue;
+        p_sd->p_sys->p_sdt->p_last = p_queue;
+        p_sd->p_sys->i_progress = intf_UserProgress( p_sd, "MTP transfer...", "Adding...", 0.0, 0 );
+        p_sd->p_sys->i_current = 0;
+        p_sd->p_sys->i_total = 1;
+        p_sd->p_sys->p_sdt->i_success = 0;
+    }
+    if( ( asprintf( &psz_status, "Adding file %d/%d", p_sd->p_sys->i_current, p_sd->p_sys->i_total ) ) != -1 )
+    {
+        intf_ProgressUpdate( p_sd, p_sd->p_sys->i_progress, psz_status, 100.0*(float)(p_sd->p_sys->i_current-1)/(float)p_sd->p_sys->i_total, 0 );
+        free( psz_status );
+    }
+    vlc_mutex_unlock( &p_sd->p_sys->p_sdt->lock );
+    return VLC_SUCCESS;
+}
+
+static int ItemPut( services_discovery_t *p_sd, char *psz_path, bool b_pl_operation, int *i_new_id )
+{
+    LIBMTP_raw_device_t *p_rawdevices;
+    LIBMTP_folder_t *p_folders, *p_folder_curr;
+    int i_numrawdevices;
+    int i_ret;
+    input_item_t *p_input;
+    LIBMTP_track_t *p_track;
+    int i_parent;
+    bool b_error = false;
+    char *psz_extension = NULL;
+
+    i_ret = LIBMTP_Detect_Raw_Devices( &p_rawdevices, &i_numrawdevices );
+    if ( i_ret == 0 && i_numrawdevices > 0 && p_rawdevices != NULL )
+    {
+        for( i_ret = 0; i_ret < i_numrawdevices; i_ret++ )
+        {
+            if( p_sd->p_sys->i_bus == p_rawdevices[i_ret].bus_location &&
+                p_sd->p_sys->i_dev == p_rawdevices[i_ret].devnum &&
+                p_sd->p_sys->i_product_id == p_rawdevices[i_ret].device_entry.product_id )
+            {
+                if( ( p_sd->p_sys->p_device = LIBMTP_Open_Raw_Device( &p_rawdevices[i_ret] ) ) != NULL )
+                    i_ret = i_numrawdevices;
+                else
+                {
+                    free( p_rawdevices );
+                    return VLC_EGENERIC;
+                }
+            }
+        }
+        free( p_rawdevices );
+        if( p_sd->p_sys->p_device == NULL )
+            return VLC_EGENERIC;
+    }
+    else
+    {
+        free( p_rawdevices );
+        return VLC_EGENERIC;
+    }
+    p_input = input_item_New( p_sd, psz_path, psz_path );
+    input_Preparse( p_sd, p_input );
+    if( !( p_track = LIBMTP_new_track_t() ) )
+        b_error = true;
+    if( input_item_GetTitle( p_input ) )
+    {
+        if( !( p_track->title = strdup( input_item_GetTitle( p_input ) ) ) )
+            b_error = true;
+    }
+    else
+        p_track->title = NULL;
+    if( input_item_GetAlbum( p_input ) )
+    {
+        if( !( p_track->album = strdup( input_item_GetAlbum( p_input ) ) ) )
+            b_error = true;
+    }
+    else
+        p_track->album = NULL;
+    if( input_item_GetArtist( p_input ) )
+    {
+        if( !( p_track->artist = strdup( input_item_GetArtist( p_input ) ) ) )
+            b_error = true;
+    }
+    else
+        p_track->artist = NULL;
+    if( input_item_GetGenre( p_input ) )
+    {
+        if( !( p_track->genre = strdup( input_item_GetGenre( p_input ) ) ) )
+            b_error = true;
+    }
+    else
+        p_track->genre = NULL;
+    if( input_item_GetDate( p_input ) )
+    {
+        if( !( p_track->date = strdup( input_item_GetDate( p_input ) ) ) )
+            b_error = true;
+    }
+    else
+        p_track->date = NULL;
+    p_track->filetype = FindFiletype( psz_path );
+    psz_extension = rindex( psz_path, '.' );
+    if( asprintf( &p_track->filename, "%s - %s%s", input_item_GetArtist( p_input ) ? input_item_GetArtist( p_input ) : N_( "Unknown Artist" ), input_item_GetTitle( p_input ) ? input_item_GetTitle( p_input ) : N_( "Unknown Track" ), psz_extension ? psz_extension : "\0" ) == -1 )
+        b_error = true;
+#ifdef HAVE_SYS_STAT_H
+    struct stat st;
+    if( utf8_stat( psz_path, &st ) )
+    {
+        msg_Err( p_sd, "fstat: %m" );
+        b_error = true;
+    }
+    p_track->filesize = st.st_size;
+#endif
+    if( b_error )
+    {
+        LIBMTP_destroy_track_t( p_track );
+        LIBMTP_Release_Device( p_sd->p_sys->p_device );
+        p_sd->p_sys->p_device = NULL;
+        vlc_gc_decref( p_input );
+        return VLC_ENOMEM;
+    }
+    p_track->tracknumber = input_item_GetTrackNum( p_input ) ? atoi( input_item_GetTrackNum( p_input ) ) : 0;
+    p_track->duration = input_item_GetDuration( p_input );
+    p_folders = LIBMTP_Get_Folder_List( p_sd->p_sys->p_device );
+    p_folder_curr = LIBMTP_Find_Folder( p_folders, p_sd->p_sys->p_device->default_music_folder );
+    if( p_folder_curr != NULL )
+        p_folder_curr = p_folder_curr->child;
+    while( p_folder_curr != NULL && strcmp( p_folder_curr->name, p_track->artist ?
+                                                                p_track->artist : N_( "Unknown Artist" ) ) )
+        p_folder_curr = p_folder_curr->sibling;
+    if( p_folder_curr == NULL )
+        i_parent = LIBMTP_Create_Folder( p_sd->p_sys->p_device, p_track->artist ?
+                                                                p_track->artist : N_( "Unknown Artist" ),
+                                                                p_sd->p_sys->p_device->default_music_folder,
+                                                                p_sd->p_sys->p_device->storage->id );
+    else
+        i_parent = p_folder_curr->folder_id;
+    p_folders = LIBMTP_Get_Folder_List( p_sd->p_sys->p_device );
+    p_folder_curr = LIBMTP_Find_Folder( p_folders, i_parent );
+    if( !strcmp( p_folder_curr->name, p_track->artist ? p_track->artist : N_( "Unknown Artist" ) ) )
+    {
+        if( p_folder_curr != NULL )
+            p_folder_curr = p_folder_curr->child;
+        while( p_folder_curr != NULL && strcmp( p_folder_curr->name, p_track->album ?
+                                                                    p_track->album :
+                                                                    N_( "Unknown Album" ) ) )
+            p_folder_curr = p_folder_curr->sibling;
+        if( p_folder_curr == NULL )
+        {
+            i_parent = LIBMTP_Create_Folder( p_sd->p_sys->p_device, p_track->album ?
+                                                                    p_track->album :
+                                                                    N_( "Unknown Album" ), i_parent,
+                                                                    p_sd->p_sys->p_device->storage->id );
+            p_folders = LIBMTP_Get_Folder_List( p_sd->p_sys->p_device );
+            p_folder_curr = LIBMTP_Find_Folder( p_folders, i_parent );
+            if( strcmp( p_folder_curr->name, p_track->album ? p_track->album : N_( "Unknown Album" ) ) )
+            {
+                LIBMTP_Delete_Object( p_sd->p_sys->p_device, i_parent );
+                i_parent = p_folder_curr->parent_id;
+            }
+        }
+        else
+            i_parent = p_folder_curr->folder_id;
+    }
+    else
+    {
+        LIBMTP_Delete_Object( p_sd->p_sys->p_device, i_parent );
+        i_parent = p_sd->p_sys->p_device->default_music_folder;
+    }
+    if( i_parent == 0 )
+        i_parent = p_sd->p_sys->p_device->default_music_folder;
+    p_track->parent_id = i_parent;
+    p_track->storage_id = p_sd->p_sys->p_device->storage->id;
+    msg_Info( p_sd, "Sending file: %s", psz_path );
+    if( LIBMTP_Send_Track_From_File( p_sd->p_sys->p_device, psz_path, p_track, ProgressUpdate, (void *)"Adding" ) != 0 )
+    {
+        LIBMTP_destroy_track_t( p_track );
+        LIBMTP_destroy_folder_t( p_folders );
+        LIBMTP_Release_Device( p_sd->p_sys->p_device );
+        p_sd->p_sys->p_device = NULL;
+        vlc_gc_decref( p_input );
+        return VLC_EGENERIC;
+    }
+    LIBMTP_Release_Device( p_sd->p_sys->p_device );
+    LIBMTP_destroy_folder_t( p_folders );
+    p_sd->p_sys->p_device = NULL;
+    free( p_input->psz_uri );
+    if( asprintf( &p_input->psz_uri, "mtp://%d:%d:%d:%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, psz_extension ? psz_extension : "\0" ) == -1 )
+    {
+        LIBMTP_destroy_track_t( p_track );
+        vlc_gc_decref( p_input );
+        return VLC_ENOMEM;
+    }
+    if( i_new_id )
+        *i_new_id = p_track->item_id;
+    LIBMTP_destroy_track_t( p_track );
+    if( b_pl_operation )
+        services_discovery_AddItem( p_sd, p_input, NULL );
+    vlc_gc_decref( p_input );
+    p_sd->p_sys->i_count++;
+    if( p_sd->p_sys->i_count == 1 )
+    {
+        if( !( p_sd->p_sys->pi_id_list = malloc( p_sd->p_sys->i_count * sizeof( int ) ) ) )
+            return VLC_ENOMEM;
+    }
+    else
+    {
+        if( !( p_sd->p_sys->pi_id_list = realloc( p_sd->p_sys->pi_id_list, p_sd->p_sys->i_count * sizeof( int ) ) ) )
+            return VLC_ENOMEM;
+    }
+    p_sd->p_sys->pi_id_list[p_sd->p_sys->i_count - 1] = p_input->i_id;
+    msg_Info( p_sd, "File added succesfully..." );
+    return VLC_SUCCESS;
+}
+
+/*****************************************************************************
+ * ItemGet: file save callback
+ *****************************************************************************/
+static int ItemGetCallback( services_discovery_t *p_sd, const char *psz_dir, int i_id )
+{
+    mtp_queue_t *p_queue;
+    char *psz_status;
+
+    if( !( p_queue = malloc( sizeof( mtp_queue_t ) ) ) )
+        return VLC_ENOMEM;
+    if( !( p_queue->psz_string = strdup( psz_dir ) ) )
+    {
+        free( p_queue );
+        return VLC_ENOMEM;
+    }
+    p_queue->i_int = i_id;
+    p_queue->i_action = MTP_FILE_SAVE;
+    p_queue->p_next = NULL;
+    vlc_mutex_lock( &p_sd->p_sys->p_sdt->lock );
+    if( p_sd->p_sys->p_sdt->p_last )
+    {
+        p_sd->p_sys->p_sdt->p_last->p_next = p_queue;
+        p_sd->p_sys->p_sdt->p_last = p_queue;
+        p_sd->p_sys->i_total++;
+    }
+    else
+    {
+        p_sd->p_sys->p_sdt->p_first = p_queue;
+        p_sd->p_sys->p_sdt->p_last = p_queue;
+        p_sd->p_sys->i_progress = intf_UserProgress( p_sd, "MTP transfer...", "Saving...", 0.0, 0 );
+        p_sd->p_sys->i_current = 0;
+        p_sd->p_sys->i_total = 1;
+        p_sd->p_sys->p_sdt->i_success = 0;
+    }
+    if( ( asprintf( &psz_status, "Saving file %d/%d", p_sd->p_sys->i_current, p_sd->p_sys->i_total ) ) != -1 )
+    {
+        intf_ProgressUpdate( p_sd, p_sd->p_sys->i_progress, psz_status, 100.0*(float)(p_sd->p_sys->i_current-1)/(float)p_sd->p_sys->i_total, 0 );
+        free( psz_status );
+    }
+    vlc_mutex_unlock( &p_sd->p_sys->p_sdt->lock );
+    return VLC_SUCCESS;
+}
+
+static int ItemGet( services_discovery_t *p_sd, char *psz_dir, int i_id )
+{
+    LIBMTP_raw_device_t *p_rawdevices;
+    LIBMTP_track_t *p_track;
+    char *psz_topath = NULL;
+    int i_ret;
+    int i_numrawdevices;
+    uint32_t i_track_id;
+    input_item_t *p_input;
+
+    if( ( p_input = input_item_GetById( ( vlc_object_t * )p_sd, i_id ) ) )
+        sscanf( p_input->psz_uri, "mtp://%*d:%*d:%*d:%d", &i_track_id );
+    else
+        return VLC_EGENERIC;
+    i_ret = LIBMTP_Detect_Raw_Devices( &p_rawdevices, &i_numrawdevices );
+    if ( i_ret == 0 && i_numrawdevices > 0 && p_rawdevices != NULL )
+    {
+        for( i_ret = 0; i_ret < i_numrawdevices; i_ret++ )
+        {
+            if( p_sd->p_sys->i_bus == p_rawdevices[i_ret].bus_location &&
+                p_sd->p_sys->i_dev == p_rawdevices[i_ret].devnum &&
+                p_sd->p_sys->i_product_id == p_rawdevices[i_ret].device_entry.product_id )
+            {
+                if( ( p_sd->p_sys->p_device = LIBMTP_Open_Raw_Device( &p_rawdevices[i_ret] ) ) != NULL )
+                    i_ret = i_numrawdevices;
+                else
+                {
+                    free( p_rawdevices );
+                    return VLC_EGENERIC;
+                }
+            }
+        }
+        free( p_rawdevices );
+        if( p_sd->p_sys->p_device == NULL )
+            return VLC_EGENERIC;
+    }
+    else
+    {
+        free( p_rawdevices );
+        return VLC_EGENERIC;
+    }
+    if( ( p_track = LIBMTP_Get_Trackmetadata( p_sd->p_sys->p_device, i_track_id ) ) == NULL )
+    {
+        LIBMTP_Release_Device( p_sd->p_sys->p_device );
+        p_sd->p_sys->p_device = NULL;
+        return VLC_ENOMEM;
+    }
+    i_ret = strlen( psz_dir );
+    if( psz_dir[i_ret-1] == DIR_SEP_CHAR )
+    {
+        if( asprintf( &psz_topath, "%s%s", psz_dir, p_track->filename ) == -1 )
+        {
+            LIBMTP_destroy_track_t( p_track );
+            LIBMTP_Release_Device( p_sd->p_sys->p_device );
+            p_sd->p_sys->p_device = NULL;
+            return VLC_ENOMEM;
+        }
+    }
+    if( ( i_ret = LIBMTP_Get_Track_To_File( p_sd->p_sys->p_device, i_track_id, psz_topath ? psz_topath : psz_dir, ProgressUpdate, (void *)"Saving" ) ) == 0 )
+        msg_Info( p_sd, "File saved succesfully: %s", psz_topath );
+    free( psz_topath );
+    LIBMTP_destroy_track_t( p_track );
+    LIBMTP_Release_Device( p_sd->p_sys->p_device );
+    p_sd->p_sys->p_device = NULL;
+    if( i_ret != 0 )
+        return VLC_EGENERIC;
+    else
+        return VLC_SUCCESS;
+}
+
+/*****************************************************************************
+ * ItemDelete: file delete callback
+ *****************************************************************************/
+static int ItemDeleteCallback( services_discovery_t *p_sd, int i_id )
+{
+    mtp_queue_t *p_queue;
+
+    if( !( p_queue = malloc( sizeof( mtp_queue_t ) ) ) )
+        return VLC_ENOMEM;
+    p_queue->i_int = i_id;
+    p_queue->psz_string = NULL;
+    p_queue->i_action = MTP_FILE_DELETE;
+    p_queue->p_next = NULL;
+    vlc_mutex_lock( &p_sd->p_sys->p_sdt->lock );
+    if( p_sd->p_sys->p_sdt->p_last )
+    {
+        p_sd->p_sys->p_sdt->p_last->p_next = p_queue;
+        p_sd->p_sys->p_sdt->p_last = p_queue;
+    }
+    else
+    {
+        p_sd->p_sys->p_sdt->p_first = p_queue;
+        p_sd->p_sys->p_sdt->p_last = p_queue;
+    }
+    vlc_mutex_unlock( &p_sd->p_sys->p_sdt->lock );
+    return VLC_SUCCESS;
+}
+
+static int ItemDelete( services_discovery_t *p_sd, int i_id, bool b_pl_operation )
+{
+    LIBMTP_raw_device_t *p_rawdevices;
+    LIBMTP_file_t *p_files;
+    LIBMTP_file_t *p_file;
+    LIBMTP_folder_t *p_folders;
+    LIBMTP_folder_t *p_folder;
+    input_item_t *p_input;
+    int i_numrawdevices;
+    int i_ret;
+    int i_i;
+    uint32_t i_track_id;
+    uint32_t i_parent;
+    bool b_found;
+
+    if( ( p_input = input_item_GetById( ( vlc_object_t * )p_sd, i_id ) ) )
+        sscanf( p_input->psz_uri, "mtp://%*d:%*d:%*d:%d", &i_track_id );
+    else
+        return VLC_EGENERIC;
+    i_ret = LIBMTP_Detect_Raw_Devices( &p_rawdevices, &i_numrawdevices );
+    if ( i_ret == 0 && i_numrawdevices > 0 && p_rawdevices != NULL )
+    {
+        for( i_ret = 0; i_ret < i_numrawdevices; i_ret++ )
+        {
+            if( p_sd->p_sys->i_bus == p_rawdevices[i_ret].bus_location &&
+                p_sd->p_sys->i_dev == p_rawdevices[i_ret].devnum &&
+                p_sd->p_sys->i_product_id == p_rawdevices[i_ret].device_entry.product_id )
+            {
+                if( ( p_sd->p_sys->p_device = LIBMTP_Open_Raw_Device( &p_rawdevices[i_ret] ) ) != NULL )
+                    i_ret = i_numrawdevices;
+                else
+                {
+                    free( p_rawdevices );
+                    return VLC_EGENERIC;
+                }
+            }
+        }
+        free( p_rawdevices );
+        if( p_sd->p_sys->p_device == NULL )
+            return VLC_EGENERIC;
+    }
+    else
+    {
+        free( p_rawdevices );
+        return VLC_EGENERIC;
+    }
+    p_files = LIBMTP_Get_Filelisting_With_Callback( p_sd->p_sys->p_device, NULL, NULL );
+    p_folders = LIBMTP_Get_Folder_List( p_sd->p_sys->p_device );
+    i_parent = p_sd->p_sys->p_device->default_music_folder;
+    if( ( p_file = LIBMTP_Get_Filemetadata( p_sd->p_sys->p_device, i_track_id ) ) != NULL )
+    {
+        i_parent = p_file->parent_id;
+        LIBMTP_destroy_file_t( p_file );
+    }
+    if( ( i_ret = LIBMTP_Delete_Object( p_sd->p_sys->p_device, i_track_id ) ) == 0 )
+    {
+        if( b_pl_operation )
+            services_discovery_RemoveItem( p_sd, p_input );
+        for( i_i = 0; i_i < 2; i_i++ )
+        {
+            b_found = false;
+            p_file = p_files;
+            while( p_file != NULL )
+            {
+                if( p_file->parent_id == i_parent && p_file->item_id != i_track_id )
+                {
+                    b_found = true;
+                    break;
+                }
+                p_file = p_file->next;
+            }
+            if( !b_found && i_parent != p_sd->p_sys->p_device->default_music_folder )
+            {
+                p_folder = LIBMTP_Find_Folder( p_folders, i_parent );
+                if( LIBMTP_Delete_Object( p_sd->p_sys->p_device, i_parent ) != 0 )
+                    msg_Err( p_sd, "Cannot delete empty folder..." );
+                else
+                    i_parent = p_folder->parent_id;
+            }
+        }
+    }
+    LIBMTP_destroy_file_t( p_files );
+    LIBMTP_destroy_folder_t( p_folders );
+    LIBMTP_Release_Device( p_sd->p_sys->p_device );
+    p_sd->p_sys->p_device = NULL;
+    if( i_ret != 0 )
+        return VLC_EGENERIC;
+    else
+        return VLC_SUCCESS;
+}
+
+/*****************************************************************************
+ * FileActionThread: the thread that handle all file operations
+ *****************************************************************************/
+static void FileActionThread( mtp_thread_t *p_sdt )
+{
+    mtp_queue_t *p_temp;
+    char *psz_status;
+    vlc_mutex_t *lock;
+
+    while( !p_sdt->b_die )
+    {
+        vlc_mutex_lock( &p_sdt->lock );
+        if( p_sdt->p_first )
+        {
+            p_sd_global->p_sys->i_current++;
+            switch( p_sdt->p_first->i_action )
+            {
+                case MTP_FILE_ADD:
+                {
+                    if( ( asprintf( &psz_status, "Adding file %d/%d", p_sd_global->p_sys->i_current, p_sd_global->p_sys->i_total ) ) != -1 )
+                    {
+                        intf_ProgressUpdate( p_sd_global, p_sd_global->p_sys->i_progress, psz_status, 100.0*(float)(p_sd_global->p_sys->i_current-1)/(float)p_sd_global->p_sys->i_total, 0 );
+                        free( psz_status );
+                    }
+                    vlc_mutex_unlock( &p_sdt->lock );
+                    lock = var_AcquireMutex( p_sdt->psz_lock_name );
+                    if( ItemPut( p_sd_global, p_sdt->p_first->psz_string, true, NULL ) != VLC_SUCCESS )
+                        msg_Err( p_sdt, "Some error occourred in add thread, item id: %d", p_sdt->p_first->i_int );
+                    else
+                        p_sdt->i_success++;
+                    vlc_mutex_unlock( lock );
+                    break;
+                }
+                case MTP_FILE_SAVE:
+                {
+                    if( ( asprintf( &psz_status, "Saving file %d/%d", p_sd_global->p_sys->i_current, p_sd_global->p_sys->i_total ) ) != -1 )
+                    {
+                        intf_ProgressUpdate( p_sd_global, p_sd_global->p_sys->i_progress, psz_status, 100.0*(float)(p_sd_global->p_sys->i_current-1)/(float)p_sd_global->p_sys->i_total, 0 );
+                        free( psz_status );
+                    }
+                    vlc_mutex_unlock( &p_sdt->lock );
+                    lock = var_AcquireMutex( p_sdt->psz_lock_name );
+                    if( ItemGet( p_sd_global, p_sdt->p_first->psz_string, p_sdt->p_first->i_int ) != VLC_SUCCESS )
+                        msg_Err( p_sdt, "Some error occourred in save thread, item id: %d", p_sdt->p_first->i_int );
+                    else
+                        p_sdt->i_success++;
+                    vlc_mutex_unlock( lock );
+                    break;
+                }
+                case MTP_FILE_DELETE:
+                {
+                    vlc_mutex_unlock( &p_sdt->lock );
+                    lock = var_AcquireMutex( p_sdt->psz_lock_name );
+                    if( ItemDelete( p_sd_global, p_sdt->p_first->i_int, true ) == VLC_SUCCESS )
+                        msg_Info( p_sdt, "File deleted succesfully..." );
+                    else
+                        msg_Err( p_sdt, "Some error occourred in delete thread, item id: %d", p_sdt->p_first->i_int );
+                    vlc_mutex_unlock( lock );
+                    break;
+                }
+            }
+            vlc_mutex_lock( &p_sdt->lock );
+            p_temp = p_sdt->p_first;
+            p_sdt->p_first = p_sdt->p_first->p_next;
+            if( p_sd_global->p_sys->i_progress != -1 && intf_ProgressIsCancelled( p_sd_global, p_sd_global->p_sys->i_progress ) )
+            {
+                while( p_sdt->p_first != NULL )
+                {
+                    p_temp = p_sdt->p_first->p_next;
+                    free( p_sdt->p_first );
+                    p_sdt->p_first = p_temp;
+                }
+                p_sdt->p_first = NULL;
+                msg_Info( p_sdt, "File transfer cancelled" );
+            }
+            if( p_temp )
+            {
+                if( p_temp->psz_string )
+                    free( p_temp->psz_string );
+                free( p_temp );
+            }
+            if( !p_sdt->p_first && !intf_ProgressIsCancelled( p_sd_global, p_sd_global->p_sys->i_progress ) )
+            {
+                if( ( asprintf( &psz_status, "Transferred %d/%d files succesfully", p_sdt->i_success, p_sd_global->p_sys->i_total ) ) != -1 )
+                {
+                    intf_ProgressUpdate( p_sd_global, p_sd_global->p_sys->i_progress, psz_status, 100.0, 0 );
+                    p_sd_global->p_sys->i_progress = -1;
+                    free( psz_status );
+                }
+            }
+            if( !p_sdt->p_first )
+                p_sdt->p_last = NULL;
+            vlc_mutex_unlock( &p_sdt->lock );
+        }
+        else
+            vlc_mutex_unlock( &p_sdt->lock );
+        msleep( 500 );
+    }
+    free( p_sdt->psz_lock_name );
+    vlc_object_release( p_sdt );
+}
+
+/*****************************************************************************
+ * 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->p_device = p_device;
+        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, NULL ) ) == NULL )
+            msg_Info( p_sd, "No tracks on the device..." );
+        else
+        {
+            if( !( p_sd->p_sys->pi_id_list = malloc( p_sd->p_sys->i_tracks_num * sizeof( int ) ) ) )
+                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 );
+            }
+        }
+        FixName( p_sd, psz_name );
+        p_sd->p_sys->psz_name = psz_name;
+        LIBMTP_Release_Device( p_device );
+        p_sd->p_sys->p_device = NULL;
+        if( ( asprintf( &p_sd->p_sys->p_sdt->psz_lock_name, "mtp-%d%d%d", p_sd->p_sys->i_bus, p_sd->p_sys->i_dev, p_sd->p_sys->i_product_id ) ) == -1 )
+            return VLC_ENOMEM;
+#ifdef HAVE_SYS_STAT_H
+        p_sd->b_read_only = false;
+#else
+        msg_Err( p_sd, "sys/stat.h not found, read-only mode..." );
+#endif
+        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://%d:%d:%d:%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 );
+    /* TODO This one doesn't show in the playlist... */
+    p_input->i_duration = p_track->duration;
+    services_discovery_AddItem( p_sd, p_input, NULL );
+    p_sd->p_sys->pi_id_list[p_sd->p_sys->i_count++] = p_input->i_id;
+    vlc_gc_decref( p_input );
+}
+
+static void CloseDevice( services_discovery_t *p_sd )
+{
+    input_item_t *p_input;
+    int i_i;
+
+    if( p_sd->p_sys->pi_id_list != NULL )
+    {
+        for( i_i = 0; i_i < p_sd->p_sys->i_count; i_i++ )
+        {
+            p_input = input_item_GetById( ( vlc_object_t * )p_sd, p_sd->p_sys->pi_id_list[i_i] );
+            if( p_input != NULL )
+                services_discovery_RemoveItem( p_sd, p_input );
+        }
+        free( p_sd->p_sys->pi_id_list );
+    }
+    p_sd->b_read_only = true;
+}
+
+static int CountTracks( uint64_t const sent, uint64_t const total, void const * const data )
+{
+    VLC_UNUSED( sent );
+    VLC_UNUSED( total );
+    VLC_UNUSED( data );
+    p_sd_global->p_sys->i_tracks_num = total;
+    return 0;
+}
+
+static void FixName( services_discovery_t *p_sd, char *psz_name )
+{
+    input_item_t *p_input;
+
+    if( ( p_input = input_item_GetById( ( vlc_object_t * )p_sd, p_sd->p_sys->i_id ) ) )
+    {
+            free( p_input->psz_name );
+            p_input->psz_name = strdup( psz_name );
+    }
+    services_discovery_SetLocalizedName( p_sd, psz_name );
+}
+
+static void SetSDId( services_discovery_t *p_sd )
+{
+    input_item_t *p_input;
+
+    p_input = input_item_New( p_sd, NULL, NULL );
+    p_sd->p_sys->i_id = p_input->i_id - 1;
+    vlc_gc_decref( p_input );
+    do
+    {
+        if( ( p_input = input_item_GetById( ( vlc_object_t * )p_sd, p_sd->p_sys->i_id ) ) )
+        {
+            if( !strcmp( p_input->psz_name, N_( "MTP Device" ) ) )
+                return;
+            else
+                p_sd->p_sys->i_id--;
+        }
+    }
+    while( p_sd->p_sys->i_id >= 0 );
+}
+
+LIBMTP_filetype_t FindFiletype ( const char * filename )
+{
+    LIBMTP_filetype_t filetype;
+    char *psz_extension;
+
+    psz_extension = rindex( filename, '.' );
+    if ( !psz_extension )
+        psz_extension = NULL; /* This should not happen... */
+    else
+        ++psz_extension;
+    if ( !strcasecmp ( psz_extension, "wav" ) )
+        filetype = LIBMTP_FILETYPE_WAV;
+    else if ( !strcasecmp ( psz_extension, "mp3" ) )
+        filetype = LIBMTP_FILETYPE_MP3;
+    else if ( !strcasecmp ( psz_extension, "wma" ) )
+        filetype = LIBMTP_FILETYPE_WMA;
+    else if ( !strcasecmp ( psz_extension, "ogg" ) )
+        filetype = LIBMTP_FILETYPE_OGG;
+    else if ( !strcasecmp ( psz_extension, "wma" ) )
+        filetype = LIBMTP_FILETYPE_WMA;
+    else if ( !strcasecmp ( psz_extension, "aac" ) )
+        filetype = LIBMTP_FILETYPE_AAC;
+    else if ( !strcasecmp ( psz_extension, "mp2" ) )
+        filetype = LIBMTP_FILETYPE_MP2;
+    else if ( !strcasecmp ( psz_extension, "flac" ) )
+        filetype = LIBMTP_FILETYPE_FLAC;
+    else if ( !strcasecmp ( psz_extension, "m4a" ) )
+        filetype = LIBMTP_FILETYPE_M4A;
+    else
+        filetype = LIBMTP_FILETYPE_UNKNOWN;
+    return filetype;
+}
+
+static int ProgressUpdate( uint64_t const sent, uint64_t const total, void const * const data )
+{
+    char *psz_status;
+    if( ( asprintf( &psz_status, "%s file %d/%d", (char *)data, p_sd_global->p_sys->i_current, p_sd_global->p_sys->i_total ) ) != -1 )
+    {
+        if( p_sd_global->p_sys->i_progress != -1 )
+            intf_ProgressUpdate( p_sd_global, p_sd_global->p_sys->i_progress, psz_status, 100.0*(float)(p_sd_global->p_sys->i_current-1)/(float)p_sd_global->p_sys->i_total+100.0*(float)sent/(float)total/(float)p_sd_global->p_sys->i_total, 0 );
+            free( psz_status );
+    }
+    return 0;
+}
-- 
1.5.4.3

