From 03b2b8e86e557322322eae0649d79120476db401 Mon Sep 17 00:00:00 2001
From: Fabio Ritrovato <exsephiroth87@gmail.com>
Date: Sat, 28 Mar 2009 17:21:21 +0100
Subject: [PATCH] GSoC: MTP - Expand modules functionalities

---
 modules/access/mtp.c             |    9 +
 modules/services_discovery/mtp.c |  805 +++++++++++++++++++++++++++++++++++++-
 2 files changed, 806 insertions(+), 8 deletions(-)

diff --git a/modules/access/mtp.c b/modules/access/mtp.c
index 759d4b8..38e71b2 100644
--- a/modules/access/mtp.c
+++ b/modules/access/mtp.c
@@ -108,6 +108,8 @@ static int Open( vlc_object_t *p_this )
     LIBMTP_mtpdevice_t *p_device;
     int i_numrawdevices;
     int i_ret;
+    vlc_mutex_t *mtp_lock;
+    vlc_value_t val;
 
     /* Update default_pts to a suitable value for file access */
     var_Create( p_access, "file-caching", VLC_VAR_INTEGER|VLC_VAR_DOINHERIT );
@@ -125,6 +127,10 @@ static int Open( vlc_object_t *p_this )
             i_dev == p_rawdevices[i].devnum &&
             i_product_id == p_rawdevices[i].device_entry.product_id )
         {
+            var_Create( p_access->p_libvlc, "mtp_lock", VLC_VAR_MUTEX );
+            var_Get( p_access->p_libvlc, "mtp_lock", &val );
+            mtp_lock = val.p_address;
+            vlc_mutex_lock( mtp_lock );
             if( ( p_device = LIBMTP_Open_Raw_Device( &p_rawdevices[i] )
                 ) != NULL )
             {
@@ -133,6 +139,7 @@ static int Open( vlc_object_t *p_this )
                 {
                     LIBMTP_Release_Device( p_device );
                     free( p_rawdevices );
+                    vlc_mutex_unlock( mtp_lock );
                     return VLC_ENOMEM;
                 }
                 else
@@ -141,11 +148,13 @@ static int Open( vlc_object_t *p_this )
                     LIBMTP_Get_File_To_File( p_device, i_track_id,
                                              p_access->psz_path, NULL, NULL );
                     LIBMTP_Release_Device( p_device );
+                    vlc_mutex_unlock( mtp_lock );
                     i = i_numrawdevices;
                 }
             }
             else
             {
+                vlc_mutex_unlock( mtp_lock );
                 free( p_rawdevices );
                 return VLC_EGENERIC;
             }
diff --git a/modules/services_discovery/mtp.c b/modules/services_discovery/mtp.c
index ecffca5..82aaa9e 100644
--- a/modules/services_discovery/mtp.c
+++ b/modules/services_discovery/mtp.c
@@ -29,7 +29,7 @@
 #include <vlc_plugin.h>
 #include <errno.h>
 #include <vlc_charset.h>
-#include <vlc_interface.h>
+#include <vlc_dialog.h>
 #include <vlc_services_discovery.h>
 
 #ifdef HAVE_SYS_STAT_H
@@ -38,6 +38,13 @@
 
 #include "libmtp.h"
 
+enum
+{
+    MTP_FILE_ADD,
+    MTP_FILE_SAVE,
+    MTP_FILE_DELETE
+};
+
 /*****************************************************************************
  * Module descriptor
  *****************************************************************************/
@@ -61,17 +68,55 @@ vlc_module_end()
 
 static void *Run( void * );
 
+static void Operation( const vlc_event_t * , void * );
+static int ItemGet( services_discovery_t *, char *, input_item_t * );
+static int ItemPut( services_discovery_t *, input_item_t *, bool, int * );
+static int ItemDelete( services_discovery_t *, input_item_t *, bool );
+
 static int AddDevice( services_discovery_t *, LIBMTP_raw_device_t * );
-static void AddTrack( services_discovery_t *, LIBMTP_track_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 int ProgressUpdate( uint64_t const, uint64_t const, void const * const );
+LIBMTP_filetype_t FindFiletype ( const char * );
+static int WaitForMeta( vlc_object_t *, const char *, vlc_value_t,
+                        vlc_value_t, void * );
 
 /*****************************************************************************
  * Local structures
  *****************************************************************************/
 
+typedef struct mtp_queue_t
+{
+    char *psz_string;
+    input_item_t *p_item;
+    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;
+    int i_current;
+    int i_total;
+    bool b_wait;
+    dialog_progress_bar_t *p_progress;
+    services_discovery_t *p_sd;
+}mtp_thread_t;
+
+typedef struct
+{
+    services_discovery_t *p_sd;
+    char *psz_string;
+}progress_update_data;
+
 struct services_discovery_sys_t
 {
+    LIBMTP_mtpdevice_t *p_device;
     int i_tracks_num;
     input_item_t **pp_items;
     int i_count;
@@ -80,11 +125,14 @@ struct services_discovery_sys_t
     uint8_t i_dev;
     uint16_t i_product_id;
     vlc_thread_t thread;
+    mtp_thread_t *p_sdt;
 };
 
-static vlc_mutex_t mtp_lock = VLC_STATIC_MUTEX;
+static vlc_mutex_t *mtp_lock;
 static bool b_mtp_initialized = false;
 
+static void *FileActionThread( vlc_object_t * );
+
 /*****************************************************************************
  * Open: initialize and create stuff
  *****************************************************************************/
@@ -92,22 +140,62 @@ static int Open( vlc_object_t *p_this )
 {
     services_discovery_t *p_sd = ( services_discovery_t * )p_this;
     services_discovery_sys_t *p_sys;
+    vlc_value_t val;
 
     if( !( p_sys = malloc( sizeof( services_discovery_sys_t ) ) ) )
         return VLC_ENOMEM;
     p_sd->p_sys = p_sys;
     p_sys->psz_name = NULL;
+    p_sys->p_device = NULL;
+    p_sd->b_read_only = true;
 
-    vlc_mutex_lock( &mtp_lock );
+    var_Create( p_sd->p_libvlc, "mtp_lock", VLC_VAR_MUTEX );
+    var_Get( p_sd->p_libvlc, "mtp_lock", &val );
+    mtp_lock = val.p_address;
+    vlc_mutex_lock( mtp_lock );
     if( !b_mtp_initialized )
     {
         LIBMTP_Init();
         b_mtp_initialized = true;
     }
-    vlc_mutex_unlock( &mtp_lock );
+    vlc_mutex_unlock( mtp_lock );
 
+    vlc_event_manager_register_event_type( &p_sd->event_manager,
+            vlc_ServicesDiscoveryItemPut );
+    vlc_event_attach( services_discovery_EventManager( p_sd ),
+              vlc_ServicesDiscoveryItemPut, Operation, p_sd );
+    vlc_event_manager_register_event_type( &p_sd->event_manager,
+            vlc_ServicesDiscoveryItemGet );
+    vlc_event_attach( services_discovery_EventManager( p_sd ),
+              vlc_ServicesDiscoveryItemGet, Operation, p_sd );
+    vlc_event_manager_register_event_type( &p_sd->event_manager,
+            vlc_ServicesDiscoveryItemDelete );
+    vlc_event_attach( services_discovery_EventManager( p_sd ),
+              vlc_ServicesDiscoveryItemDelete, Operation, p_sd );
+
+    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;
+    p_sys->p_sdt->p_sd = p_sd;
+    vlc_mutex_init( &p_sys->p_sdt->lock );
+    p_sys->p_sdt->b_die = false;
+    if( vlc_thread_create( p_sys->p_sdt, "mtp file action", FileActionThread,
+                       VLC_THREAD_PRIORITY_LOW ) )
+    {
+        free( p_sys );
+        return VLC_ENOMEM;
+    }
     if (vlc_clone (&p_sys->thread, Run, p_sd, VLC_THREAD_PRIORITY_LOW))
     {
+        vlc_object_kill( p_sys->p_sdt );
+        vlc_mutex_destroy( &p_sys->p_sdt->lock );
+        vlc_thread_join( p_sys->p_sdt );
+        vlc_object_release( p_sys->p_sdt );
         free (p_sys);
         return VLC_EGENERIC;
     }
@@ -121,10 +209,23 @@ 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->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);
+    vlc_event_detach( services_discovery_EventManager( p_sd ),
+              vlc_ServicesDiscoveryItemPut, Operation, p_sd );
+    vlc_event_detach( services_discovery_EventManager( p_sd ),
+              vlc_ServicesDiscoveryItemGet, Operation, p_sd );
+    vlc_event_detach( services_discovery_EventManager( p_sd ),
+              vlc_ServicesDiscoveryItemDelete, Operation, p_sd );
     free( p_sd->p_sys );
 }
 
@@ -170,10 +271,615 @@ static void *Run( void *data )
 }
 
 /*****************************************************************************
+ * Operation: parse sd events and build a queue to handle them
+ *****************************************************************************/
+static void Operation( const vlc_event_t * p_event, void * user_data )
+{
+    services_discovery_t *p_sd = ( services_discovery_t * )user_data;
+    mtp_queue_t *p_queue;
+
+    if( !( p_queue = malloc( sizeof( mtp_queue_t ) ) ) )
+        return;
+    p_queue->p_next = NULL;
+    vlc_mutex_lock( &p_sd->p_sys->p_sdt->lock );
+    switch( p_event->type )
+    {
+        case vlc_ServicesDiscoveryItemPut:
+        {
+            if( !( strncmp( p_event->u.services_discovery_item_put.psz_string,
+                            "file://", 7 ) ) )
+                p_queue->psz_string = strdup(
+                        p_event->u.services_discovery_item_put.psz_string + 7 );
+            else
+                p_queue->psz_string = strdup(
+                            p_event->u.services_discovery_item_put.psz_string );
+            if( !p_queue->psz_string ||
+                FindFiletype( p_queue->psz_string ) == LIBMTP_FILETYPE_UNKNOWN )
+            {
+                free( p_queue );
+                vlc_mutex_unlock( &p_sd->p_sys->p_sdt->lock );
+                return;
+            }
+            p_queue->i_action = MTP_FILE_ADD;
+            break;
+        }
+        case vlc_ServicesDiscoveryItemGet:
+        {
+            if( !( p_queue->psz_string = strdup(
+                    p_event->u.services_discovery_item_get.psz_string ) ) )
+            {
+                free( p_queue );
+                vlc_mutex_unlock( &p_sd->p_sys->p_sdt->lock );
+                return;
+            }
+            p_queue->p_item = p_event->u.services_discovery_item_get.p_item;
+            p_queue->i_action = MTP_FILE_SAVE;
+            break;
+        }
+        case vlc_ServicesDiscoveryItemDelete:
+        {
+            p_queue->psz_string = NULL;
+            p_queue->p_item = p_event->u.services_discovery_item_delete.p_item;
+            p_queue->i_action = MTP_FILE_DELETE;
+            break;
+        }
+        default:
+            break;
+    }
+    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->p_sdt->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->p_sdt->i_current = 0;
+        p_sd->p_sys->p_sdt->i_total = 1;
+        p_sd->p_sys->p_sdt->i_success = 0;
+    }
+    vlc_mutex_unlock( &p_sd->p_sys->p_sdt->lock );
+}
+
+/*****************************************************************************
+ * FileActionThread: handle one file operation at a time
+ *****************************************************************************/
+static void *FileActionThread( vlc_object_t *p_this )
+{
+    mtp_queue_t *p_temp;
+    char *psz_status;
+    mtp_thread_t *p_sdt = ( mtp_thread_t * )p_this;
+    services_discovery_t *p_sd = p_sdt->p_sd;
+
+    while( !p_sdt->b_die )
+    {
+        vlc_mutex_lock( &p_sdt->lock );
+        if( p_sdt->p_first )
+        {
+            p_sdt->i_current++;
+            switch( p_sdt->p_first->i_action )
+            {
+                case MTP_FILE_ADD:
+                {
+                    if( p_sdt->i_current == 1 )
+                        p_sdt->p_progress = dialog_ProgressCreate( p_sdt,
+                                                                   "MTP transfer...",
+                                                                   "Adding...",
+                                                                   "Cancel" );
+                    if( ( asprintf( &psz_status, "Adding file %d/%d",
+                                    p_sdt->i_current, p_sdt->i_total ) ) != -1 &&
+                                    p_sdt->p_progress )
+                    {
+                        dialog_ProgressSet( p_sdt->p_progress, psz_status,
+                                            (float)(p_sdt->i_current-1)/
+                                            (float)p_sdt->i_total );
+                        free( psz_status );
+                    }
+                    p_sdt->b_wait = true;
+                    vlc_mutex_unlock( &p_sdt->lock );
+                    input_item_t *p_input = input_item_New( p_sd,
+                                                   p_sdt->p_first->psz_string,
+                                                   p_sdt->p_first->psz_string );
+                    input_thread_t *p_thread = input_CreateThread( p_sdt, p_input );
+                    var_AddCallback( p_thread, "intf-event", WaitForMeta, p_sdt );
+                    for( ;; )
+                    {
+                        vlc_mutex_lock( &p_sdt->lock );
+                        if( !p_sdt->b_wait )
+                        {
+                            var_DelCallback( p_thread, "intf-event", WaitForMeta,
+                                             p_sdt );
+                            input_StopThread( p_thread, true );
+                            vlc_thread_join( p_thread );
+                            vlc_object_release( p_thread );
+                            vlc_mutex_unlock( &p_sdt->lock );
+                            break;
+                        }
+                        vlc_mutex_unlock( &p_sdt->lock );
+                        msleep( 5000 );
+                    }
+                    vlc_mutex_lock( mtp_lock );
+                    if( ItemPut( p_sd, p_input, true, NULL ) != VLC_SUCCESS )
+                        msg_Err( p_sdt,
+                              "Some error occourred in add thread, item id: %d",
+                              p_sdt->p_first->p_item->i_id );
+                    else
+                        p_sdt->i_success++;
+                    vlc_mutex_unlock( mtp_lock );
+                    break;
+                }
+                case MTP_FILE_SAVE:
+                {
+                    if( p_sdt->i_current == 1 )
+                        p_sdt->p_progress = dialog_ProgressCreate( p_sdt,
+                                                                   "MTP transfer...",
+                                                                   "Saving...",
+                                                                   "Cancel" );
+                    if( ( asprintf( &psz_status, "Saving file %d/%d",
+                                    p_sdt->i_current, p_sdt->i_total ) ) != -1 &&
+                                    p_sdt->p_progress )
+                    {
+                        dialog_ProgressSet( p_sdt->p_progress, psz_status,
+                                            (float)(p_sdt->i_current-1)/
+                                            (float)p_sdt->i_total );
+                        free( psz_status );
+                    }
+                    vlc_mutex_unlock( &p_sdt->lock );
+                    vlc_mutex_lock( mtp_lock );
+                    if( ItemGet( p_sd, p_sdt->p_first->psz_string,
+                                 p_sdt->p_first->p_item ) != VLC_SUCCESS )
+                        msg_Err( p_sdt,
+                             "Some error occourred in save thread, item id: %d",
+                             p_sdt->p_first->p_item->i_id );
+                    else
+                        p_sdt->i_success++;
+                    vlc_mutex_unlock( mtp_lock );
+                    break;
+                }
+                case MTP_FILE_DELETE:
+                {
+                    vlc_mutex_unlock( &p_sdt->lock );
+                    vlc_mutex_lock( mtp_lock );
+                    if( ItemDelete( p_sd, p_sdt->p_first->p_item, true ) == VLC_SUCCESS )
+                        msg_Dbg( p_sdt, "File deleted succesfully" );
+                    else
+                        msg_Err( p_sdt,
+                           "Some error occourred in delete thread, item id: %d",
+                           p_sdt->p_first->p_item->i_id );
+                    vlc_mutex_unlock( mtp_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_sdt->p_progress && dialog_ProgressCancelled( p_sdt->p_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;
+                dialog_ProgressDestroy( p_sdt->p_progress );
+                msg_Dbg( 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 )
+                p_sdt->p_last = NULL;
+            vlc_mutex_unlock( &p_sdt->lock );
+        }
+        else
+            vlc_mutex_unlock( &p_sdt->lock );
+        msleep( 500000 );
+    }
+    vlc_object_release( p_sdt );
+    return NULL;
+}
+
+
+/*****************************************************************************
+ * ItemPut, ItemGet, ItemDelete
+ *****************************************************************************/
+static int ItemPut( services_discovery_t *p_sd, input_item_t *p_input,
+                    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;
+    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;
+    }
+    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( input_item_GetURI( p_input ) );
+    psz_extension = rindex( input_item_GetURI( p_input ), '.' );
+    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( input_item_GetURI( p_input ), &st ) )
+    {
+        msg_Err( p_sd, "fstat: %m" );
+        b_error = true;
+    }
+    p_track->filesize = st.st_size;
+#endif
+    progress_update_data data;
+    data.p_sd = p_sd;
+    if( !( data.psz_string = strdup( "Adding" ) ) )
+        b_error = true;
+    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_Dbg( p_sd, "Sending file: %s", input_item_GetURI( p_input ) );
+    if( LIBMTP_Send_Track_From_File( p_sd->p_sys->p_device, input_item_GetURI( p_input ),
+        p_track, ProgressUpdate, (void *)&data ) != 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 );
+        free( data.psz_string );
+        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 );
+    free( data.psz_string );
+    if( asprintf( &p_input->psz_uri, "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, 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 );
+    p_sd->p_sys->i_count++;
+    if( p_sd->p_sys->i_count == 1 )
+    {
+        if( !( p_sd->p_sys->pp_items = malloc( sizeof( input_item_t * ) ) ) )
+            return VLC_ENOMEM;
+    }
+    else
+    {
+        if( !( p_sd->p_sys->pp_items = realloc( p_sd->p_sys->pp_items,
+                                     p_sd->p_sys->i_count * sizeof( input_item_t ) ) ) )
+            return VLC_ENOMEM;
+    }
+    p_sd->p_sys->pp_items[p_sd->p_sys->i_count - 1] = p_input;
+    msg_Dbg( p_sd, "File added succesfully" );
+    return VLC_SUCCESS;
+}
+
+static int ItemGet( services_discovery_t *p_sd, char *psz_dir, input_item_t *p_input )
+{
+    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;
+
+    if( sscanf( p_input->psz_uri, "mtp://%*d:%*d:%*d:%d", &i_track_id ) != 1 )
+        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 );
+    progress_update_data data;
+    data.p_sd = p_sd;
+    if( psz_dir[i_ret-1] == DIR_SEP_CHAR )
+    {
+        if( asprintf( &psz_topath, "%s%s", psz_dir, p_track->filename ) == -1 ||
+            !( data.psz_string = strdup( "Saving" ) ) )
+        {
+            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 *)&data ) ) == 0 )
+        msg_Dbg( p_sd, "File saved succesfully: %s", psz_topath );
+    free( psz_topath );
+    free( data.psz_string );
+    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;
+}
+
+static int ItemDelete( services_discovery_t *p_sd, input_item_t *p_input,
+                       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;
+    int i_numrawdevices;
+    int i_ret;
+    int i_i;
+    uint32_t i_track_id;
+    uint32_t i_parent;
+    bool b_found;
+
+    if( sscanf( p_input->psz_uri, "mtp://%*d:%*d:%*d:%d", &i_track_id ) != 1 )
+        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;
+}
+
+/*****************************************************************************
  * Everything else
  *****************************************************************************/
-static int AddDevice( services_discovery_t *p_sd,
-                      LIBMTP_raw_device_t *p_raw_device )
+static int AddDevice( services_discovery_t *p_sd, LIBMTP_raw_device_t *p_raw_device )
 {
     char *psz_name = NULL;
     LIBMTP_mtpdevice_t *p_device;
@@ -186,6 +892,7 @@ static int AddDevice( services_discovery_t *p_sd,
                 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;
@@ -212,6 +919,12 @@ static int AddDevice( services_discovery_t *p_sd,
         }
         p_sd->p_sys->psz_name = psz_name;
         LIBMTP_Release_Device( p_device );
+        p_sd->p_sys->p_device = NULL;
+#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
@@ -279,13 +992,89 @@ static void CloseDevice( services_discovery_t *p_sd )
         }
         free( pp_items );
     }
+    p_sd->b_read_only = true;
 }
 
 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;
+    services_discovery_t *p_sd = ( services_discovery_t * )data;
     p_sd->p_sys->i_tracks_num = total;
     return 0;
 }
+
+static int ProgressUpdate( uint64_t const sent, uint64_t const total,
+                           void const * const data )
+{
+    char *psz_status;
+    progress_update_data *udata = ( progress_update_data* )data;
+    services_discovery_t *p_sd = udata->p_sd;
+    char *psz_string = udata->psz_string;
+    if( ( asprintf( &psz_status, "%s file %d/%d", psz_string,
+                    p_sd->p_sys->p_sdt->i_current,
+                    p_sd->p_sys->p_sdt->i_total ) ) != -1 )
+    {
+        if( p_sd->p_sys->p_sdt->p_progress &&
+            !dialog_ProgressCancelled( p_sd->p_sys->p_sdt->p_progress ) )
+            dialog_ProgressSet( p_sd->p_sys->p_sdt->p_progress,
+                     psz_status, (float)(p_sd->p_sys->p_sdt->i_current-1)/
+                     (float)p_sd->p_sys->p_sdt->i_total+
+                     (float)sent/(float)total/
+                     (float)p_sd->p_sys->p_sdt->i_total );
+            free( psz_status );
+    }
+    return 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;
+    else
+        ++psz_extension;
+    if( 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;
+    }
+    else
+            filetype = LIBMTP_FILETYPE_UNKNOWN;
+    return filetype;
+}
+
+static int WaitForMeta( vlc_object_t *p_this, const char *psz_var,
+                vlc_value_t oldval, vlc_value_t newval, void *p_data )
+{
+    VLC_UNUSED( p_this ); VLC_UNUSED( psz_var ); VLC_UNUSED( oldval );
+    mtp_thread_t *p_sdt = (mtp_thread_t *)p_data;
+    if( newval.i_int == INPUT_EVENT_ITEM_META )
+    {
+        vlc_mutex_lock( &p_sdt->lock );
+        p_sdt->b_wait = false;
+        vlc_mutex_unlock( &p_sdt->lock );
+    }
+    return VLC_SUCCESS;
+}
-- 
1.5.4.3

