1 /***************************************************************************
2 * copyright : (C) 2006 Andy Kelk <andy@mopoke.co.uk> *
3 ***************************************************************************/
5 /***************************************************************************
7 * This program is free software; you can redistribute it and/or modify *
8 * it under the terms of the GNU General Public License as published by *
9 * the Free Software Foundation; either version 2 of the License, or *
10 * (at your option) any later version. *
12 ***************************************************************************/
15 * Based on njb mediadevice with some code hints from the libmtp
21 * @author Andy Kelk <andy@mopoke.co.uk>
22 * @see http://libmtp.sourceforge.net/
25 #define DEBUG_PREFIX "MtpMediaDevice"
27 #include "config-amarok.h"
28 #include "mtpmediadevice.h"
33 AMAROK_EXPORT_PLUGIN( MtpMediaDevice
)
37 #include <metabundle.h>
38 #include <statusbar/statusbar.h>
39 #include <statusbar/popupMessage.h>
42 #include <kapplication.h>
43 #include <kiconloader.h>
45 #include <kmessagebox.h>
51 #include <q3listview.h>
59 * MtpMediaDevice Class
62 MtpMediaDevice::MtpMediaDevice() : MediaDevice()
64 m_name
= i18n("MTP Media Device");
69 m_hasMountPoint
= false;
72 m_transcodeAlways
= false;
73 m_transcodeRemove
= false;
75 m_customButton
= true;
78 KToolBarButton
*customButton
= MediaBrowser::instance()->getToolBar()->getButton( MediaBrowser::CUSTOM
);
79 customButton
->setText( i18n("Special device functions") );
80 QToolTip::remove( customButton
);
81 customButton
->setToolTip( i18n( "Special functions of your device" ) );
83 mtpFileTypes
[LIBMTP_FILETYPE_WAV
] = "wav";
84 mtpFileTypes
[LIBMTP_FILETYPE_MP3
] = "mp3";
85 mtpFileTypes
[LIBMTP_FILETYPE_WMA
] = "wma";
86 mtpFileTypes
[LIBMTP_FILETYPE_OGG
] = "ogg";
87 mtpFileTypes
[LIBMTP_FILETYPE_AUDIBLE
] = "aa"; // audible
88 mtpFileTypes
[LIBMTP_FILETYPE_MP4
] = "mp4";
89 mtpFileTypes
[LIBMTP_FILETYPE_UNDEF_AUDIO
] = "undef-audio";
90 mtpFileTypes
[LIBMTP_FILETYPE_WMV
] = "wmv";
91 mtpFileTypes
[LIBMTP_FILETYPE_AVI
] = "avi";
92 mtpFileTypes
[LIBMTP_FILETYPE_MPEG
] = "mpg";
93 mtpFileTypes
[LIBMTP_FILETYPE_ASF
] = "asf";
94 mtpFileTypes
[LIBMTP_FILETYPE_QT
] = "mov";
95 mtpFileTypes
[LIBMTP_FILETYPE_UNDEF_VIDEO
] = "undef-video";
96 mtpFileTypes
[LIBMTP_FILETYPE_JPEG
] = "jpg";
97 mtpFileTypes
[LIBMTP_FILETYPE_JFIF
] = "jpg";
98 mtpFileTypes
[LIBMTP_FILETYPE_TIFF
] = "tiff";
99 mtpFileTypes
[LIBMTP_FILETYPE_BMP
] = "bmp";
100 mtpFileTypes
[LIBMTP_FILETYPE_GIF
] = "gif";
101 mtpFileTypes
[LIBMTP_FILETYPE_PICT
] = "pict";
102 mtpFileTypes
[LIBMTP_FILETYPE_PNG
] = "png";
103 mtpFileTypes
[LIBMTP_FILETYPE_VCALENDAR1
] = "vcs"; // vcal1
104 mtpFileTypes
[LIBMTP_FILETYPE_VCALENDAR2
] = "vcs"; // vcal2
105 mtpFileTypes
[LIBMTP_FILETYPE_VCARD2
] = "vcf"; // vcard2
106 mtpFileTypes
[LIBMTP_FILETYPE_VCARD3
] = "vcf"; // vcard3
107 mtpFileTypes
[LIBMTP_FILETYPE_WINDOWSIMAGEFORMAT
] = "wim"; // windows image format
108 mtpFileTypes
[LIBMTP_FILETYPE_WINEXEC
] = "exe";
109 mtpFileTypes
[LIBMTP_FILETYPE_TEXT
] = "txt";
110 mtpFileTypes
[LIBMTP_FILETYPE_HTML
] = "html";
111 mtpFileTypes
[LIBMTP_FILETYPE_UNKNOWN
] = "unknown";
113 m_newTracks
= new Q3PtrList
<MediaItem
>;
117 MtpMediaDevice::init( MediaBrowser
*parent
)
119 MediaDevice::init( parent
);
123 MtpMediaDevice::isConnected()
125 return !( m_device
== 0 );
129 * File types that we support
132 MtpMediaDevice::supportedFiletypes()
134 return m_supportedFiles
;
139 MtpMediaDevice::progressCallback( uint64_t const sent
, uint64_t const total
, void const * const data
)
144 kapp
->processEvents( 100 );
146 MtpMediaDevice
*dev
= (MtpMediaDevice
*)(data
);
148 if( dev
->isCanceled() )
150 debug() << "Canceling transfer operation";
151 dev
->setCanceled( true );
159 * Copy a track to the device
162 *MtpMediaDevice::copyTrackToDevice( const MetaBundle
&bundle
)
166 QString genericError
= i18n( "Could not send track" );
168 LIBMTP_track_t
*trackmeta
= LIBMTP_new_track_t();
169 trackmeta
->item_id
= 0;
170 debug() << "filetype : " << bundle
.fileType();
171 if( bundle
.fileType() == MetaBundle::mp3
)
173 trackmeta
->filetype
= LIBMTP_FILETYPE_MP3
;
175 else if( bundle
.fileType() == MetaBundle::ogg
)
177 trackmeta
->filetype
= LIBMTP_FILETYPE_OGG
;
179 else if( bundle
.fileType() == MetaBundle::wma
)
181 trackmeta
->filetype
= LIBMTP_FILETYPE_WMA
;
183 else if( bundle
.fileType() == MetaBundle::mp4
)
185 trackmeta
->filetype
= LIBMTP_FILETYPE_MP4
;
189 // Couldn't recognise an Amarok filetype.
190 // fallback to checking the extension (e.g. .wma, .ogg, etc)
191 debug() << "No filetype found by Amarok filetype";
193 const QString extension
= bundle
.url().path().section( ".", -1 ).toLower();
195 int libmtp_type
= m_supportedFiles
.findIndex( extension
);
196 if( libmtp_type
>= 0 )
198 int keyIndex
= mtpFileTypes
.values().findIndex( extension
);
199 libmtp_type
= mtpFileTypes
.keys()[keyIndex
];
200 trackmeta
->filetype
= (LIBMTP_filetype_t
) libmtp_type
;
201 debug() << "set filetype to " << libmtp_type
<< " based on extension of ." << extension
;
205 debug() << "We don't support the extension ." << extension
;
206 Amarok::StatusBar::instance()->shortLongMessage(
208 i18n( "Cannot determine a valid file type" ),
209 KDE::StatusBar::Error
215 if( bundle
.title().isEmpty() )
217 trackmeta
->title
= qstrdup( i18n( "Unknown title" ).toUtf8() );
221 trackmeta
->title
= qstrdup( bundle
.title().toUtf8() );
224 if( bundle
.album().isEmpty() )
226 trackmeta
->album
= qstrdup( i18n( "Unknown album" ).toUtf8() );
230 trackmeta
->album
= qstrdup( bundle
.album().string().toUtf8() );
233 if( bundle
.artist().isEmpty() )
235 trackmeta
->artist
= qstrdup( i18n( "Unknown artist" ).toUtf8() );
239 trackmeta
->artist
= qstrdup( bundle
.artist().string().toUtf8() );
242 if( bundle
.genre().isEmpty() )
244 trackmeta
->genre
= qstrdup( i18n( "Unknown genre" ).toUtf8() );
248 trackmeta
->genre
= qstrdup( bundle
.genre().string().toUtf8() );
251 if( bundle
.year() > 0 )
254 QTextOStream( &date
) << bundle
.year() << "0101T0000.0";
255 trackmeta
->date
= qstrdup( date
.toUtf8() );
259 trackmeta
->date
= qstrdup( "00010101T0000.0" );
262 if( bundle
.track() > 0 )
264 trackmeta
->tracknumber
= bundle
.track();
266 if( bundle
.length() > 0 )
268 // Multiply by 1000 since this is in milliseconds
269 trackmeta
->duration
= bundle
.length() * 1000;
271 if( !bundle
.filename().isEmpty() )
273 trackmeta
->filename
= qstrdup( bundle
.filename().toUtf8() );
275 trackmeta
->filesize
= bundle
.filesize();
277 // try and create the requested folder structure
278 uint32_t parent_id
= 0;
279 if( !m_folderStructure
.isEmpty() )
281 parent_id
= checkFolderStructure( bundle
);
284 debug() << "Couldn't create new parent (" << m_folderStructure
<< ")";
285 Amarok::StatusBar::instance()->shortLongMessage(
287 i18n( "Cannot create parent folder. Check your structure." ),
288 KDE::StatusBar::Error
295 parent_id
= getDefaultParentId();
297 debug() << "Parent id : " << parent_id
;
299 m_critical_mutex
.lock();
300 debug() << "Sending track... " << bundle
.url().path().toUtf8();
301 int ret
= LIBMTP_Send_Track_From_File(
302 m_device
, bundle
.url().path().toUtf8(), trackmeta
,
303 progressCallback
, this, parent_id
305 m_critical_mutex
.unlock();
309 debug() << "Could not write file " << ret
;
310 Amarok::StatusBar::instance()->shortLongMessage(
312 i18n( "File write failed" ),
313 KDE::StatusBar::Error
318 MetaBundle
temp( bundle
);
319 MtpTrack
*taggedTrack
= new MtpTrack( trackmeta
);
320 taggedTrack
->setBundle( temp
);
321 taggedTrack
->setFolderId( parent_id
);
323 LIBMTP_destroy_track_t( trackmeta
);
325 kapp
->processEvents( 100 );
327 // add track to view and to new tracks list
328 MediaItem
*newItem
= addTrackToView( taggedTrack
);
329 m_newTracks
->append( newItem
);
334 * Get the cover image for a track, convert it to a format supported on the
335 * device and set it as the cover art.
338 MtpMediaDevice::sendAlbumArt( Q3PtrList
<MediaItem
> *items
)
341 image
= CollectionDB::instance()->albumImage(items
->first()->bundle()->artist(), items
->first()->bundle()->album(), false, 100);
342 if( ! image
.endsWith( "@nocover.png" ) )
344 debug() << "image " << image
<< " found for " << items
->first()->bundle()->album();
345 QByteArray
*imagedata
= getSupportedImage( image
);
348 debug() << "Cannot generate a supported image format";
351 if( imagedata
->size() )
353 m_critical_mutex
.lock();
354 LIBMTP_album_t
*album_object
= getOrCreateAlbum( items
);
357 LIBMTP_filesampledata_t
*imagefile
= LIBMTP_new_filesampledata_t();
358 imagefile
->data
= (char *) imagedata
->data();
359 imagefile
->size
= imagedata
->size();
360 imagefile
->filetype
= LIBMTP_FILETYPE_JPEG
;
361 int ret
= LIBMTP_Send_Representative_Sample( m_device
, album_object
->album_id
, imagefile
);
364 debug() << "image send failed : " << ret
;
367 m_critical_mutex
.unlock();
373 MtpMediaDevice::getDefaultParentId( void )
375 // Decide which folder to send it to:
376 // If the device gave us a parent_folder setting, we use it
377 uint32_t parent_id
= 0;
378 if( m_default_parent_folder
)
380 parent_id
= m_default_parent_folder
;
382 // Otherwise look for a folder called "Music"
383 else if( m_folders
!= 0 )
385 parent_id
= folderNameToID( "Music", m_folders
);
388 debug() << "Parent folder could not be found. Going to use top level.";
391 // Give up and don't set a parent folder, let the device deal with it
394 debug() << "No folders found. Going to use top level.";
400 * Takes path to an existing cover image and converts it to a format
401 * supported on the device
404 *MtpMediaDevice::getSupportedImage( QString path
)
409 debug() << "Will convert image to " << m_format
;
412 const QImage
original( path
);
415 QImage
newformat( original
);
416 QByteArray
*newimage
= new QByteArray();
417 QBuffer
buffer( *newimage
);
418 buffer
.open( QIODevice::WriteOnly
);
419 if( newformat
.save( &buffer
, m_format
.ascii() ) )
428 * Update cover art for a number of tracks
431 MtpMediaDevice::updateAlbumArt( Q3PtrList
<MediaItem
> *items
)
435 if( m_format
== 0 ) // no supported image types. Don't even bother.
438 setCanceled( false );
440 kapp
->processEvents( 100 );
441 QMap
< QString
, Q3PtrList
<MediaItem
> > albumList
;
443 for( MtpMediaItem
*it
= dynamic_cast<MtpMediaItem
*>(items
->first()); it
&& !(m_canceled
); it
= dynamic_cast<MtpMediaItem
*>(items
->next()) )
446 if( it
->type() == MediaItem::TRACK
)
448 albumList
[ it
->bundle()->album() ].append( it
);
450 if( it
->type() == MediaItem::ALBUM
)
452 debug() << "look, we get albums too!";
456 setProgress( i
, albumList
.count() );
457 kapp
->processEvents( 100 );
458 QMap
< QString
, Q3PtrList
<MediaItem
> >::Iterator it
;
459 for( it
= albumList
.begin(); it
!= albumList
.end(); ++it
)
461 sendAlbumArt( &it
.data() );
464 kapp
->processEvents( 100 );
470 * Retrieve existing or create new album object.
473 *MtpMediaDevice::getOrCreateAlbum( Q3PtrList
<MediaItem
> *items
)//uint32_t track_id, const MetaBundle &bundle )
475 LIBMTP_album_t
*album_object
= 0;
476 uint32_t albumid
= 0;
478 QMap
<uint32_t,MtpAlbum
*>::Iterator it
;
479 for( it
= m_idToAlbum
.begin(); it
!= m_idToAlbum
.end(); ++it
)
481 if( it
.data()->album() == items
->first()->bundle()->album() )
483 albumid
= it
.data()->id();
489 debug() << "reusing existing album " << albumid
;
490 album_object
= LIBMTP_Get_Album( m_device
, albumid
);
491 if( album_object
== 0 )
493 debug() << "retrieving album failed.";
497 uint32_t trackCount
= album_object
->no_tracks
;
498 for( MtpMediaItem
*it
= dynamic_cast<MtpMediaItem
*>(items
->first()); it
; it
= dynamic_cast<MtpMediaItem
*>(items
->next()) )
501 for( i
= 0; i
< album_object
->no_tracks
; i
++ )
503 if( album_object
->tracks
[i
] == it
->track()->id() )
511 debug() << "adding track " << it
->track()->id() << " to existing album " << albumid
;
512 album_object
->no_tracks
++;
513 album_object
->tracks
= (uint32_t *)realloc( album_object
->tracks
, album_object
->no_tracks
* sizeof( uint32_t ) );
514 album_object
->tracks
[ ( album_object
->no_tracks
- 1 ) ] = it
->track()->id();
517 if( trackCount
!= album_object
->no_tracks
) // album needs an update
519 ret
= LIBMTP_Update_Album( m_device
, album_object
);
521 debug() << "updating album failed : " << ret
;
526 debug() << "creating new album ";
527 album_object
= LIBMTP_new_album_t();
528 album_object
->name
= qstrdup( items
->first()->bundle()->album().string().toUtf8() );
529 album_object
->tracks
= (uint32_t *) malloc(items
->count() * sizeof(uint32_t));
531 for( MtpMediaItem
*it
= dynamic_cast<MtpMediaItem
*>(items
->first()); it
; it
= dynamic_cast<MtpMediaItem
*>(items
->next()) )
532 album_object
->tracks
[i
++] = it
->track()->id();
533 album_object
->no_tracks
= items
->count();
534 ret
= LIBMTP_Create_New_Album( m_device
, album_object
, 0 );
537 debug() << "creating album failed : " << ret
;
540 m_idToAlbum
[ album_object
->album_id
] = new MtpAlbum( album_object
);
546 * Check (and optionally create) the folder structure to put a
547 * track into. Return the (possibly new) parent folder ID
550 MtpMediaDevice::checkFolderStructure( const MetaBundle
&bundle
, bool create
)
552 QString artist
= bundle
.artist();
553 if( artist
.isEmpty() )
554 artist
= i18n( "Unknown Artist" );
555 if( bundle
.compilation() == MetaBundle::CompilationYes
)
556 artist
= i18n( "Various Artists" );
557 QString album
= bundle
.album();
558 if( album
.isEmpty() )
559 album
= i18n( "Unknown Album" );
560 QString genre
= bundle
.genre();
561 if( genre
.isEmpty() )
562 genre
= i18n( "Unknown Genre" );
563 m_critical_mutex
.lock();
564 uint32_t parent_id
= getDefaultParentId();
565 QStringList folders
= QStringList::split( "/", m_folderStructure
); // use slash as a dir separator
566 QString completePath
;
567 for( QStringList::Iterator it
= folders
.begin(); it
!= folders
.end(); ++it
)
569 if( (*it
).isEmpty() )
571 // substitute %a , %b , %g
572 (*it
).replace( QRegExp( "%a" ), artist
)
573 .replace( QRegExp( "%b" ), album
)
574 .replace( QRegExp( "%g" ), genre
);
575 // check if it exists
576 uint32_t check_folder
= subfolderNameToID( (*it
).toUtf8(), m_folders
, parent_id
);
577 // create if not exists (if requested)
578 if( check_folder
== 0 )
582 check_folder
= createFolder( (*it
).toUtf8() , parent_id
);
583 if( check_folder
== 0 )
585 m_critical_mutex
.unlock();
591 m_critical_mutex
.unlock();
595 completePath
+= (*it
).toUtf8() + '/';
597 parent_id
= check_folder
;
599 m_critical_mutex
.unlock();
600 debug() << "Folder path : " << completePath
;
606 * Create a new mtp folder
609 MtpMediaDevice::createFolder( const char *name
, uint32_t parent_id
)
611 debug() << "Creating new folder '" << name
<< "' as a child of "<< parent_id
;
612 char *name_copy
= qstrdup( name
);
613 uint32_t new_folder_id
= LIBMTP_Create_Folder( m_device
, name_copy
, parent_id
);
615 debug() << "New folder ID: " << new_folder_id
;
616 if( new_folder_id
== 0 )
618 debug() << "Attempt to create folder '" << name
<< "' failed.";
623 return new_folder_id
;
627 * Recursively search the folder list for a matching one under the specified
628 * parent ID and return the child's ID
631 MtpMediaDevice::subfolderNameToID( const char *name
, LIBMTP_folder_t
*folderlist
, uint32_t parent_id
)
635 if( folderlist
== 0 )
638 if( !strcasecmp( name
, folderlist
->name
) && folderlist
->parent_id
== parent_id
)
639 return folderlist
->folder_id
;
641 if( ( i
= ( subfolderNameToID( name
, folderlist
->child
, parent_id
) ) ) )
643 if( ( i
= ( subfolderNameToID( name
, folderlist
->sibling
, parent_id
) ) ) )
650 * Recursively search the folder list for a matching one
654 MtpMediaDevice::folderNameToID( char *name
, LIBMTP_folder_t
*folderlist
)
658 if( folderlist
== 0 )
661 if( !strcasecmp( name
, folderlist
->name
) )
662 return folderlist
->folder_id
;
664 if( ( i
= ( folderNameToID( name
, folderlist
->child
) ) ) )
666 if( ( i
= ( folderNameToID( name
, folderlist
->sibling
) ) ) )
673 * Get a list of selected items, download them to a temporary location and
677 MtpMediaDevice::downloadSelectedItemsToCollection()
679 Q3PtrList
<MediaItem
> items
;
680 m_view
->getSelectedLeaves( 0, &items
);
682 KTempDir
tempdir( QString() );
683 tempdir
.setAutoDelete( true );
685 QString genericError
= i18n( "Could not copy track from device." );
688 total
= items
.count();
694 setProgress( progress
, total
);
695 for( MtpMediaItem
*it
= dynamic_cast<MtpMediaItem
*>(items
.first()); it
&& !(m_canceled
); it
= dynamic_cast<MtpMediaItem
*>(items
.next()) )
697 if( it
->type() == MediaItem::TRACK
)
699 QString filename
= tempdir
.name() + it
->bundle()->filename();
700 int ret
= LIBMTP_Get_Track_To_File(
701 m_device
, it
->track()->id(), filename
.toUtf8(),
702 progressCallback
, this
706 debug() << "Get Track failed: " << ret
;
707 Amarok::StatusBar::instance()->shortLongMessage(
709 i18n( "Could not copy track from device." ),
710 KDE::StatusBar::Error
717 setProgress( progress
);
723 setProgress( progress
, total
);
727 //PORT 2.0 CollectionView::instance()->organizeFiles( urls, i18n( "Move Files To Collection" ), false );
732 * Write any pending changes to the device, such as database changes
735 MtpMediaDevice::synchronizeDevice()
737 updateAlbumArt( m_newTracks
);
738 m_newTracks
->clear();
743 * Find an existing track
746 *MtpMediaDevice::trackExists( const MetaBundle
&bundle
)
748 MediaItem
*artist
= dynamic_cast<MediaItem
*>( m_view
->findItem( bundle
.artist(), 0 ) );
751 MediaItem
*album
= dynamic_cast<MediaItem
*>( artist
->findItem( bundle
.album() ) );
754 MediaItem
*track
= dynamic_cast<MediaItem
*>( album
->findItem( bundle
.title() ) );
759 uint32_t folderId
= checkFolderStructure( bundle
, false );
760 MediaItem
*file
= m_fileNameToItem
[ QString( "%1/%2" ).arg( folderId
).arg( bundle
.filename() ) ];
767 * Create a new playlist
770 *MtpMediaDevice::newPlaylist( const QString
&name
, MediaItem
*parent
, Q3PtrList
<MediaItem
> items
)
773 MtpMediaItem
*item
= new MtpMediaItem( parent
, this );
774 item
->setType( MediaItem::PLAYLIST
);
775 item
->setText( 0, name
);
776 item
->setPlaylist( new MtpPlaylist() );
778 addToPlaylist( item
, 0, items
);
780 if( ! isTransferring() )
781 m_view
->rename( item
, 0 );
787 * Add an item to a playlist
790 MtpMediaDevice::addToPlaylist( MediaItem
*mlist
, MediaItem
*after
, Q3PtrList
<MediaItem
> items
)
793 MtpMediaItem
*list
= dynamic_cast<MtpMediaItem
*>( mlist
);
801 order
= after
->m_order
+ 1;
802 it
= dynamic_cast<MtpMediaItem
*>(after
->nextSibling());
807 it
= dynamic_cast<MtpMediaItem
*>( list
->firstChild() );
810 for( ; it
; it
= dynamic_cast<MtpMediaItem
*>( it
->nextSibling() ) )
812 it
->m_order
+= items
.count();
815 for( MtpMediaItem
*it
= dynamic_cast<MtpMediaItem
*>(items
.first() );
817 it
= dynamic_cast<MtpMediaItem
*>( items
.next() ) )
823 if( it
->parent() == list
)
833 list
->insertItem(it
);
840 add
= new MtpMediaItem( list
, after
);
844 add
= new MtpMediaItem( list
, this );
849 add
->setType( MediaItem::PLAYLISTITEM
);
850 add
->setTrack( it
->track() );
851 add
->setBundle( new MetaBundle( *(it
->bundle()) ) );
852 add
->m_device
= this;
853 add
->setText( 0, it
->bundle()->artist() + " - " + it
->bundle()->title() );
854 add
->m_order
= order
;
858 // make numbering consecutive
860 for( MtpMediaItem
*it
= dynamic_cast<MtpMediaItem
*>( list
->firstChild() );
862 it
= dynamic_cast<MtpMediaItem
*>( it
->nextSibling() ) )
868 playlistFromItem( list
);
872 * When a playlist has been renamed, we must save it
875 MtpMediaDevice::playlistRenamed( Q3ListViewItem
*item
, const QString
&, int ) // SLOT
878 MtpMediaItem
*playlist
= static_cast<MtpMediaItem
*>( item
);
879 if( playlist
->type() == MediaItem::PLAYLIST
)
880 playlistFromItem( playlist
);
887 MtpMediaDevice::playlistFromItem( MtpMediaItem
*item
)
889 if( item
->childCount() == 0 )
891 m_critical_mutex
.lock();
892 LIBMTP_playlist_t
*metadata
= LIBMTP_new_playlist_t();
893 metadata
->name
= qstrdup( item
->text( 0 ).toUtf8() );
894 const int trackCount
= item
->childCount();
895 if (trackCount
> 0) {
896 uint32_t *tracks
= ( uint32_t* )malloc( sizeof( uint32_t ) * trackCount
);
898 for( MtpMediaItem
*it
= dynamic_cast<MtpMediaItem
*>(item
->firstChild());
900 it
= dynamic_cast<MtpMediaItem
*>(it
->nextSibling()) )
902 tracks
[i
] = it
->track()->id();
905 metadata
->tracks
= tracks
;
906 metadata
->no_tracks
= i
;
908 debug() << "no tracks available for playlist " << metadata
->name
910 metadata
->no_tracks
= 0;
912 QString genericError
= i18n( "Could not save playlist." );
914 uint32_t *tracks
= ( uint32_t* )malloc( sizeof( uint32_t ) * item
->childCount() );
916 for( MtpMediaItem
*it
= dynamic_cast<MtpMediaItem
*>(item
->firstChild());
918 it
= dynamic_cast<MtpMediaItem
*>(it
->nextSibling()) )
920 tracks
[i
] = it
->track()->id();
923 metadata
->tracks
= tracks
;
924 metadata
->no_tracks
= i
;
926 QString genericError
= i18n( "Could not save playlist." );
928 if( item
->playlist()->id() == 0 )
930 debug() << "creating new playlist : " << metadata
->name
;
931 int ret
= LIBMTP_Create_New_Playlist( m_device
, metadata
, 0 );
934 item
->playlist()->setId( metadata
->playlist_id
);
935 debug() << "playlist saved : " << metadata
->playlist_id
;
939 Amarok::StatusBar::instance()->shortLongMessage(
941 i18n( "Could not create new playlist on device." ),
942 KDE::StatusBar::Error
948 metadata
->playlist_id
= item
->playlist()->id();
949 debug() << "updating playlist : " << metadata
->name
;
950 int ret
= LIBMTP_Update_Playlist( m_device
, metadata
);
953 Amarok::StatusBar::instance()->shortLongMessage(
955 i18n( "Could not update playlist on device." ),
956 KDE::StatusBar::Error
960 m_critical_mutex
.unlock();
964 * Recursively remove MediaItem from the device and media view
967 MtpMediaDevice::deleteItemFromDevice(MediaItem
* item
, int flags
)
971 if( isCanceled() || !item
)
977 switch( item
->type() )
979 case MediaItem::PLAYLIST
:
980 case MediaItem::TRACK
:
985 int res
= deleteObject( dynamic_cast<MtpMediaItem
*> ( item
) );
986 if( res
>=0 && result
>= 0 )
992 case MediaItem::PLAYLISTITEM
:
997 MtpMediaItem
*parent
= dynamic_cast<MtpMediaItem
*> ( item
->parent() );
998 if( parent
&& parent
->type() == MediaItem::PLAYLIST
) {
1000 playlistFromItem( parent
);
1004 case MediaItem::ALBUM
:
1005 case MediaItem::ARTIST
:
1006 // Recurse through the lists
1012 for( MediaItem
*it
= dynamic_cast<MediaItem
*>( item
->firstChild() ); it
; it
= next
)
1014 next
= dynamic_cast<MediaItem
*>( it
->nextSibling() );
1015 int res
= deleteItemFromDevice( it
, flags
);
1016 if( res
>= 0 && result
>= 0 )
1023 delete dynamic_cast<MediaItem
*>( item
);
1032 * Actually delete a track or playlist
1035 MtpMediaDevice::deleteObject( MtpMediaItem
*deleteItem
)
1039 u_int32_t object_id
;
1040 if( deleteItem
->type() == MediaItem::PLAYLIST
)
1041 object_id
= deleteItem
->playlist()->id();
1043 object_id
= deleteItem
->track()->id();
1045 QString genericError
= i18n( "Could not delete item" );
1047 debug() << "delete this id : " << object_id
;
1049 m_critical_mutex
.lock();
1050 int status
= LIBMTP_Delete_Object( m_device
, object_id
);
1051 m_critical_mutex
.unlock();
1055 debug() << "delete object failed";
1056 Amarok::StatusBar::instance()->shortLongMessage(
1058 i18n( "Delete failed" ),
1059 KDE::StatusBar::Error
1063 debug() << "object deleted";
1065 // clear cached filename
1066 if( deleteItem
->type() == MediaItem::TRACK
)
1067 m_fileNameToItem
.remove( QString( "%1/%2" ).arg( deleteItem
->track()->folderId() ).arg( deleteItem
->bundle()->filename() ) );
1068 // remove from the media view
1070 kapp
->processEvents( 100 );
1076 * Update local cache of mtp folders
1079 MtpMediaDevice::updateFolders( void )
1081 LIBMTP_destroy_folder_t( m_folders
);
1083 m_folders
= LIBMTP_Get_Folder_List( m_device
);
1087 * Set cancellation of an operation
1090 MtpMediaDevice::cancelTransfer()
1096 * Connect to device, and populate m_view with MediaItems
1099 MtpMediaDevice::openDevice( bool silent
)
1109 QString genericError
= i18n( "Could not connect to MTP Device" );
1111 m_critical_mutex
.lock();
1113 m_device
= LIBMTP_Get_First_Device();
1114 m_critical_mutex
.unlock();
1115 if( m_device
== 0 ) {
1116 debug() << "No devices.";
1117 Amarok::StatusBar::instance()->shortLongMessage(
1119 i18n( "MTP device could not be opened" ),
1120 KDE::StatusBar::Error
1127 m_view
, SIGNAL( itemRenamed( Q3ListViewItem
*, const QString
&, int ) ),
1128 this, SLOT( playlistRenamed( Q3ListViewItem
*, const QString
&, int ) )
1131 QString modelname
= QString( LIBMTP_Get_Modelname( m_device
) );
1132 QString ownername
= QString( LIBMTP_Get_Friendlyname( m_device
) );
1134 if(! ownername
.isEmpty() )
1135 m_name
+= " (" + ownername
+ ')';
1137 m_default_parent_folder
= m_device
->default_music_folder
;
1138 debug() << "setting default parent : " << m_default_parent_folder
;
1140 MtpMediaDevice::readMtpMusic();
1142 m_critical_mutex
.lock();
1143 m_folders
= LIBMTP_Get_Folder_List( m_device
);
1144 uint16_t *filetypes
;
1145 uint16_t filetypes_len
;
1146 int ret
= LIBMTP_Get_Supported_Filetypes( m_device
, &filetypes
, &filetypes_len
);
1150 for( i
= 0; i
< filetypes_len
; i
++ )
1151 m_supportedFiles
<< mtpFileTypes
[ filetypes
[ i
] ];
1153 // find supported image types (for album art).
1154 if( m_supportedFiles
.findIndex( "jpg" ) )
1156 else if( m_supportedFiles
.findIndex( "png" ) )
1158 else if( m_supportedFiles
.findIndex( "gif" ) )
1161 m_critical_mutex
.unlock();
1167 * Start the view (add default folders such as for playlists)
1170 MtpMediaDevice::initView()
1172 if( ! isConnected() )
1174 m_playlistItem
= new MtpMediaItem( m_view
, this );
1175 m_playlistItem
->setText( 0, i18n("Playlists") );
1176 m_playlistItem
->setType( MediaItem::PLAYLISTSROOT
);
1177 m_playlistItem
->m_order
= -1;
1181 * Wrap up any loose ends and close the device
1184 MtpMediaDevice::closeDevice()
1188 // clear folder structure
1189 if( m_folders
!= 0 )
1191 m_critical_mutex
.lock();
1192 LIBMTP_destroy_folder_t( m_folders
);
1193 m_critical_mutex
.unlock();
1195 debug() << "Folders destroyed";
1201 m_critical_mutex
.lock();
1202 LIBMTP_Release_Device( m_device
);
1203 m_critical_mutex
.unlock();
1205 debug() << "Device released";
1208 // clear the cached mappings
1209 m_idToAlbum
.clear();
1210 m_idToTrack
.clear();
1211 m_fileNameToItem
.clear();
1213 // clean up the view
1220 * Get the capacity and freespace available on the device, in KB
1223 MtpMediaDevice::getCapacity( KIO::filesize_t
*total
, KIO::filesize_t
*available
)
1225 if( !isConnected() )
1228 // TODO : Follow the links so we sum up all the device's storage.
1229 *total
= m_device
->storage
->MaxCapacity
;
1230 *available
= m_device
->storage
->FreeSpaceInBytes
;
1235 * Get custom information about the device via MTP
1238 MtpMediaDevice::customClicked()
1240 QString Information
;
1243 QString batteryLevel
;
1245 QString supportedFiles
;
1247 uint8_t maxbattlevel
;
1248 uint8_t currbattlevel
;
1252 m_critical_mutex
.lock();
1253 LIBMTP_Get_Batterylevel( m_device
, &maxbattlevel
, &currbattlevel
);
1254 LIBMTP_Get_Secure_Time( m_device
, §ime
);
1255 m_critical_mutex
.unlock();
1257 batteryLevel
= i18n("Battery level: ")
1258 + QString::number( (int) ( (float) currbattlevel
/ (float) maxbattlevel
* 100.0 ) )
1260 secureTime
= i18n("Secure time: ") + sectime
;
1261 supportedFiles
= i18n("Supported file types: ")
1262 + m_supportedFiles
.join( ", " );
1264 Information
= ( i18n( "Player Information for " )
1265 + m_name
+ '\n' + batteryLevel
1266 + '\n' + secureTime
+ '\n'
1272 Information
= i18n( "Player not connected" );
1275 KMessageBox::information( 0, Information
, i18n( "Device information" ) );
1282 *MtpMediaDevice::current_device()
1288 * We use a 0 device to show a disconnected device.
1289 * This sets the device to that.
1292 MtpMediaDevice::setDisconnected()
1298 * Handle clicking of the right mouse button
1301 MtpMediaDevice::rmbPressed( Q3ListViewItem
*qitem
, const QPoint
&point
, int )
1304 enum Actions
{RENAME
, DOWNLOAD
, DELETE
, MAKE_PLAYLIST
, UPDATE_ALBUM_ART
};
1306 MtpMediaItem
*item
= static_cast<MtpMediaItem
*>( qitem
);
1309 KMenu
menu( m_view
);
1310 switch( item
->type() )
1312 case MediaItem::ARTIST
:
1313 case MediaItem::ALBUM
:
1314 case MediaItem::TRACK
:
1315 menu
.insertItem( SmallIconSet( Amarok::icon( "collection" ) ), i18n("&Copy Files to Collection..."), DOWNLOAD
);
1316 menu
.insertItem( SmallIconSet( Amarok::icon( "playlist" ) ), i18n( "Make Media Device Playlist" ), MAKE_PLAYLIST
);
1317 menu
.insertItem( SmallIconSet( Amarok::icon( "covermanager" ) ), i18n( "Refresh Cover Images" ), UPDATE_ALBUM_ART
);
1319 case MediaItem::PLAYLIST
:
1320 menu
.insertItem( SmallIconSet( Amarok::icon( "edit" ) ), i18n( "Rename" ), RENAME
);
1326 menu
.insertItem( SmallIconSet( Amarok::icon( "remove" ) ), i18n( "Delete from device" ), DELETE
);
1328 int id
= menu
.exec( point
);
1333 Q3PtrList
<MediaItem
> items
;
1334 m_view
->getSelectedLeaves( 0, &items
);
1335 QString name
= i18n( "New Playlist" );
1336 newPlaylist( name
, m_playlistItem
, items
);
1340 MediaDevice::deleteFromDevice();
1343 if( item
->type() == MediaItem::PLAYLIST
&& ! isTransferring() )
1345 m_view
->rename( item
, 0 );
1349 downloadSelectedItemsToCollection();
1351 case UPDATE_ALBUM_ART
:
1353 Q3PtrList
<MediaItem
> *items
= new Q3PtrList
<MediaItem
>;
1354 m_view
->getSelectedLeaves( 0, items
);
1356 if( items
->count() > 100 )
1358 int button
= KMessageBox::warningContinueCancel( m_parent
,
1359 i18np( "<p>You are updating cover art for 1 track. This may take some time.",
1360 "<p>You are updating cover art for %n tracks. This may take some time.",
1365 if( button
!= KMessageBox::Continue
)
1368 updateAlbumArt( items
);
1377 * Add gui elements to the device configuration
1380 MtpMediaDevice::addConfigElements( QWidget
*parent
)
1383 m_folderLabel
= new QLabel( parent
);
1384 m_folderLabel
->setText( i18n( "Folder structure:" ) );
1386 m_folderStructureBox
= new QLineEdit( parent
);
1387 m_folderStructureBox
->setText( m_folderStructure
);
1388 m_folderStructureBox
->setToolTip(
1389 i18n( "Files copied to the device will be placed in this folder." ) + '\n'
1390 + i18n( "/ is used as folder separator." ) + '\n'
1391 + i18n( "%a will be replaced with the artist name, ")
1392 + i18n( "%b with the album name," ) + '\n'
1393 + i18n( "%g with the genre.") + '\n'
1394 + i18n( "An empty path means the files will be placed unsorted in the default music folder." ) );
1398 * Remove gui elements from the device configuration
1401 MtpMediaDevice::removeConfigElements( QWidget
*parent
)
1405 delete m_folderStructureBox
;
1406 m_folderStructureBox
= 0;
1408 delete m_folderLabel
;
1413 * Save changed config after dialog commit
1416 MtpMediaDevice::applyConfig()
1418 m_folderStructure
= m_folderStructureBox
->text();
1419 setConfigString( "FolderStructure", m_folderStructure
);
1423 * Load config from the amarokrc file
1426 MtpMediaDevice::loadConfig()
1428 m_folderStructure
= configString( "FolderStructure","%a - %b" );
1432 * Add a track to the current list view
1435 *MtpMediaDevice::addTrackToView( MtpTrack
*track
, MtpMediaItem
*item
)
1437 QString artistName
= track
->bundle()->artist();
1439 MtpMediaItem
*artist
= dynamic_cast<MtpMediaItem
*>( m_view
->findItem( artistName
, 0 ) );
1442 artist
= new MtpMediaItem(m_view
);
1443 artist
->m_device
= this;
1444 artist
->setText( 0, artistName
);
1445 artist
->setType( MediaItem::ARTIST
);
1448 QString albumName
= track
->bundle()->album();
1449 MtpMediaItem
*album
= dynamic_cast<MtpMediaItem
*>( artist
->findItem( albumName
) );
1452 album
= new MtpMediaItem( artist
);
1453 album
->setText( 0, albumName
);
1454 album
->setType( MediaItem::ALBUM
);
1455 album
->m_device
= this;
1459 album
->insertItem( item
);
1462 item
= new MtpMediaItem( album
);
1463 item
->m_device
= this;
1464 QString titleName
= track
->bundle()->title();
1465 item
->setTrack( track
);
1466 item
->m_order
= track
->bundle()->track();
1467 item
->setText( 0, titleName
);
1468 item
->setType( MediaItem::TRACK
);
1469 item
->setBundle( track
->bundle() );
1470 item
->track()->setId( track
->id() );
1471 m_fileNameToItem
[ QString( "%1/%2" ).arg( track
->folderId() ).arg( track
->bundle()->filename() ) ] = item
;
1472 m_idToTrack
[ track
->id() ] = track
;
1478 * Get tracks and add them to the listview
1481 MtpMediaDevice::readMtpMusic()
1487 m_critical_mutex
.lock();
1489 QString genericError
= i18n( "Could not get music from MTP Device" );
1493 setProgress( progress
, total
); // we don't know how many tracks. fake progress bar.
1495 kapp
->processEvents( 100 );
1497 LIBMTP_track_t
*tracks
= LIBMTP_Get_Tracklisting_With_Callback( m_device
, progressCallback
, this );
1499 debug() << "Got tracks from device";
1503 debug() << "0 tracks returned. Empty device...";
1507 LIBMTP_track_t
*tmp
= tracks
;
1509 // spin through once to determine size of the list
1510 while( tracks
!= 0 )
1512 tracks
= tracks
->next
;
1515 setProgress( progress
, total
);
1517 // now process the tracks
1518 while( tracks
!= 0 )
1520 MtpTrack
*mtp_track
= new MtpTrack( tracks
);
1521 mtp_track
->readMetaData( tracks
);
1522 addTrackToView( mtp_track
);
1524 tracks
= tracks
->next
;
1525 LIBMTP_destroy_track_t( tmp
);
1527 setProgress( progress
);
1528 if( progress
% 50 == 0 )
1529 kapp
->processEvents( 100 );
1536 setProgress( total
);
1539 m_critical_mutex
.unlock();
1545 * Populate playlists
1548 MtpMediaDevice::readPlaylists()
1550 LIBMTP_playlist_t
*playlists
= LIBMTP_Get_Playlist_List( m_device
);
1552 if( playlists
!= 0 )
1554 LIBMTP_playlist_t
*tmp
;
1555 while( playlists
!= 0 )
1557 MtpMediaItem
*playlist
= new MtpMediaItem( m_playlistItem
, this );
1558 playlist
->setText( 0, QString::fromUtf8( playlists
->name
) );
1559 playlist
->setType( MediaItem::PLAYLIST
);
1560 playlist
->setPlaylist( new MtpPlaylist() );
1561 playlist
->playlist()->setId( playlists
->playlist_id
);
1563 for( i
= 0; i
< playlists
->no_tracks
; i
++ )
1565 MtpTrack
*track
= m_idToTrack
[ playlists
->tracks
[i
] ];
1566 if( track
== 0 ) // skip invalid playlist entries
1568 MtpMediaItem
*item
= new MtpMediaItem( playlist
);
1569 item
->setText( 0, track
->bundle()->artist() + " - " + track
->bundle()->title() );
1570 item
->setType( MediaItem::PLAYLISTITEM
);
1571 item
->setBundle( track
->bundle() );
1572 item
->setTrack( track
);
1574 item
->m_device
= this;
1577 playlists
= playlists
->next
;
1578 LIBMTP_destroy_playlist_t( tmp
);
1579 kapp
->processEvents( 50 );
1586 * Read existing albums
1589 MtpMediaDevice::readAlbums()
1591 LIBMTP_album_t
*albums
= LIBMTP_Get_Album_List( m_device
);
1595 LIBMTP_album_t
*tmp
;
1596 while( albums
!= 0 )
1598 m_idToAlbum
[ albums
->album_id
] = new MtpAlbum( albums
);
1600 albums
= albums
->next
;
1601 LIBMTP_destroy_album_t( tmp
);
1602 kapp
->processEvents( 50 );
1608 * Clear the current listview
1611 MtpMediaDevice::clearItems()
1620 MtpTrack::MtpTrack( LIBMTP_track_t
*track
)
1622 m_id
= track
->item_id
;
1626 * Read track properties from the device and set it on the track
1629 MtpTrack::readMetaData( LIBMTP_track_t
*track
)
1631 MetaBundle
*bundle
= new MetaBundle();
1633 if( track
->genre
!= 0 )
1634 bundle
->setGenre( AtomicString( QString::fromUtf8( track
->genre
) ) );
1635 if( track
->artist
!= 0 )
1636 bundle
->setArtist( AtomicString( QString::fromUtf8( track
->artist
) ) );
1637 if( track
->album
!= 0 )
1638 bundle
->setAlbum( AtomicString( QString::fromUtf8( track
->album
) ) );
1639 if( track
->title
!= 0 )
1640 bundle
->setTitle( AtomicString( QString::fromUtf8( track
->title
) ) );
1641 if( track
->filename
!= 0 )
1642 bundle
->setPath( AtomicString( QString::fromUtf8( track
->filename
) ) );
1644 // translate codecs to file types
1645 if( track
->filetype
== LIBMTP_FILETYPE_MP3
)
1646 bundle
->setFileType( MetaBundle::mp3
);
1647 else if( track
->filetype
== LIBMTP_FILETYPE_WMA
)
1648 bundle
->setFileType( MetaBundle::wma
);
1649 else if( track
->filetype
== LIBMTP_FILETYPE_OGG
)
1650 bundle
->setFileType( MetaBundle::ogg
);
1652 bundle
->setFileType( MetaBundle::other
);
1654 if( track
->date
!= 0 )
1655 bundle
->setYear( QString( QString::fromUtf8( track
->date
) ).mid( 0, 4 ).toUInt() );
1656 if( track
->tracknumber
> 0 )
1657 bundle
->setTrack( track
->tracknumber
);
1658 if( track
->duration
> 0 )
1659 bundle
->setLength( track
->duration
/ 1000 ); // Divide by 1000 since this is in milliseconds
1661 this->setFolderId( track
->parent_id
);
1663 this->setBundle( *bundle
);
1667 * Set this track's metabundle
1670 MtpTrack::setBundle( MetaBundle
&bundle
)
1678 MtpAlbum::MtpAlbum( LIBMTP_album_t
*album
)
1680 m_id
= album
->album_id
;
1681 m_album
= QString::fromUtf8( album
->name
);