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"
29 #include "MediaItem.h"
34 AMAROK_EXPORT_PLUGIN( MtpMediaDevice
)
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";
115 MtpMediaDevice::init( MediaBrowser
*parent
)
117 MediaDevice::init( parent
);
121 MtpMediaDevice::isConnected()
123 return !( m_device
== 0 );
127 * File types that we support
130 MtpMediaDevice::supportedFiletypes()
132 return m_supportedFiles
;
137 MtpMediaDevice::progressCallback( uint64_t const sent
, uint64_t const total
, void const * const data
)
142 kapp
->processEvents( 100 );
144 MtpMediaDevice
*dev
= (MtpMediaDevice
*)(data
);
146 if( dev
->isCanceled() )
148 debug() << "Canceling transfer operation";
149 dev
->setCanceled( true );
157 * Copy a track to the device
160 *MtpMediaDevice::copyTrackToDevice( TrackPtr track
)
164 QString genericError
= i18n( "Could not send track" );
166 LIBMTP_track_t
*trackmeta
= LIBMTP_new_track_t();
167 trackmeta
->item_id
= 0;
168 QString type
= track
.type();
169 debug() << "filetype : " << type
;
170 if( type().compare( "mp3", Qt::CaseInsensitive
) == 0 )
172 trackmeta
->filetype
= LIBMTP_FILETYPE_MP3
;
174 else if( type().compare( "ogg", Qt::CaseInsensitive
) == 0 )
176 trackmeta
->filetype
= LIBMTP_FILETYPE_OGG
;
178 else if( type().compare( "wma", Qt::CaseInsensitive
) == 0 )
180 trackmeta
->filetype
= LIBMTP_FILETYPE_WMA
;
182 else if( type().compare( "mp4", Qt::CaseInsensitive
) == 0 )
184 trackmeta
->filetype
= LIBMTP_FILETYPE_MP4
;
188 // Couldn't recognise an Amarok filetype.
189 // fallback to checking the extension (e.g. .wma, .ogg, etc)
190 debug() << "No filetype found by Amarok filetype";
192 const QString extension
= bundle
.url().path().section( ".", -1 ).toLower();
194 int libmtp_type
= m_supportedFiles
.findIndex( extension
);
195 if( libmtp_type
>= 0 )
197 int keyIndex
= mtpFileTypes
.values().findIndex( extension
);
198 libmtp_type
= mtpFileTypes
.keys()[keyIndex
];
199 trackmeta
->filetype
= (LIBMTP_filetype_t
) libmtp_type
;
200 debug() << "set filetype to " << libmtp_type
<< " based on extension of ." << extension
;
204 debug() << "We don't support the extension ." << extension
;
205 Amarok::StatusBar::instance()->shortLongMessage(
207 i18n( "Cannot determine a valid file type" ),
208 KDE::StatusBar::Error
214 if( track
.prettyName().isEmpty() )
216 trackmeta
->title
= qstrdup( i18n( "Unknown title" ).toUtf8() );
220 trackmeta
->title
= qstrdup( track
.prettyName().toUtf8() );
223 if( track
.album().prettyName().isEmpty() )
225 trackmeta
->album
= qstrdup( i18n( "Unknown album" ).toUtf8() );
229 trackmeta
->album
= qstrdup( track
.album().prettyName().toUtf8() );
232 if( track
.artist().prettyName().isEmpty() )
234 trackmeta
->artist
= qstrdup( i18n( "Unknown artist" ).toUtf8() );
238 trackmeta
->artist
= qstrdup( track
.artist().prettyName().toUtf8() );
241 if( track
.genre().prettyName().isEmpty() )
243 trackmeta
->genre
= qstrdup( i18n( "Unknown genre" ).toUtf8() );
247 trackmeta
->genre
= qstrdup( track
.genre().prettyName().toUtf8() );
250 if( track
.year() > 0 )
253 QTextOStream( &date
) << track
.year() << "0101T0000.0";
254 trackmeta
->date
= qstrdup( date
.toUtf8() );
258 trackmeta
->date
= qstrdup( "00010101T0000.0" );
261 if( track
.trackNumber() > 0 )
263 trackmeta
->tracknumber
= track
.trackNumber();
265 if( track
.length() > 0 )
267 // Multiply by 1000 since this is in milliseconds
268 trackmeta
->duration
= track
.length() * 1000;
270 if( !track
.playableUrl().filename().isEmpty() )
272 trackmeta
->filename
= qstrdup( track
.playableUrl().filename().toUtf8() );
274 trackmeta
->filesize
= track
.filesize();
276 // try and create the requested folder structure
277 uint32_t parent_id
= 0;
278 if( !m_folderStructure
.isEmpty() )
280 parent_id
= checkFolderStructure( track
);
283 debug() << "Couldn't create new parent (" << m_folderStructure
<< ")";
284 Amarok::StatusBar::instance()->shortLongMessage(
286 i18n( "Cannot create parent folder. Check your structure." ),
287 KDE::StatusBar::Error
294 parent_id
= getDefaultParentId();
296 debug() << "Parent id : " << parent_id
;
298 m_critical_mutex
.lock();
299 debug() << "Sending track... " << track
.playableUrl().path().toUtf8();
300 int ret
= LIBMTP_Send_Track_From_File(
301 m_device
, track
.playableUrl().path().toUtf8(), trackmeta
,
302 progressCallback
, this, parent_id
304 m_critical_mutex
.unlock();
308 debug() << "Could not write file " << ret
;
309 Amarok::StatusBar::instance()->shortLongMessage(
311 i18n( "File write failed" ),
312 KDE::StatusBar::Error
317 MetaBundle
temp( bundle
);
318 MtpMediaDeviceTrack
*taggedTrack
= new MtpMediaDeviceTrack( trackmeta
);
319 taggedTrack
->setFolderId( parent_id
);
321 LIBMTP_destroy_track_t( trackmeta
);
323 kapp
->processEvents();
325 // add track to view and to new tracks list
326 addTrackToCollection( taggedTrack
);
327 m_newTracks
->append( taggedTrack
);
332 * Get the cover image for a track, convert it to a format supported on the
333 * device and set it as the cover art.
336 // MtpMediaDevice::sendAlbumArt( Q3PtrList<MediaItem> *items )
339 // image = CollectionDB::instance()->albumImage(items->first()->bundle()->artist(), items->first()->bundle()->album(), false, 100);
340 // if( ! image.endsWith( "@nocover.png" ) )
342 // debug() << "image " << image << " found for " << items->first()->bundle()->album();
343 // QByteArray *imagedata = getSupportedImage( image );
344 // if( imagedata == 0 )
346 // debug() << "Cannot generate a supported image format";
349 // if( imagedata->size() )
351 // m_critical_mutex.lock();
352 // LIBMTP_album_t *album_object = getOrCreateAlbum( items );
353 // if( album_object )
355 // LIBMTP_filesampledata_t *imagefile = LIBMTP_new_filesampledata_t();
356 // imagefile->data = (char *) imagedata->data();
357 // imagefile->size = imagedata->size();
358 // imagefile->filetype = LIBMTP_FILETYPE_JPEG;
359 // int ret = LIBMTP_Send_Representative_Sample( m_device, album_object->album_id, imagefile );
362 // debug() << "image send failed : " << ret;
365 // m_critical_mutex.unlock();
371 MtpMediaDevice::getDefaultParentId( void )
373 // Decide which folder to send it to:
374 // If the device gave us a parent_folder setting, we use it
375 uint32_t parent_id
= 0;
376 if( m_default_parent_folder
)
378 parent_id
= m_default_parent_folder
;
380 // Otherwise look for a folder called "Music"
383 parent_id
= m_device
->default_music_folder
;
389 * Takes path to an existing cover image and converts it to a format
390 * supported on the device
393 *MtpMediaDevice::getSupportedImage( QString path
)
398 debug() << "Will convert image to " << m_format
;
401 const QImage
original( path
);
404 QImage
newformat( original
);
405 QByteArray
*newimage
= new QByteArray();
406 QBuffer
buffer( *newimage
);
407 buffer
.open( QIODevice::WriteOnly
);
408 if( newformat
.save( &buffer
, m_format
.ascii() ) )
417 * Update cover art for a number of tracks
420 // MtpMediaDevice::updateAlbumArt( Q3PtrList<MediaItem> *items )
424 // if( m_format == 0 ) // no supported image types. Don't even bother.
427 // setCanceled( false );
429 // kapp->processEvents( 100 );
430 // QMap< QString, Q3PtrList<MediaItem> > albumList;
432 // for( MtpMediaItem *it = dynamic_cast<MtpMediaItem*>(items->first()); it && !(m_canceled); it = dynamic_cast<MtpMediaItem*>(items->next()) )
434 // // build album list
435 // if( it->type() == MediaItem::TRACK )
437 // albumList[ it->bundle()->album() ].append( it );
439 // if( it->type() == MediaItem::ALBUM )
441 // debug() << "look, we get albums too!";
445 // setProgress( i, albumList.count() );
446 // kapp->processEvents( 100 );
447 // QMap< QString, Q3PtrList<MediaItem> >::Iterator it;
448 // for( it = albumList.begin(); it != albumList.end(); ++it )
450 // sendAlbumArt( &it.data() );
451 // setProgress( ++i );
453 // kapp->processEvents( 100 );
459 * Retrieve existing or create new album object.
462 *MtpMediaDevice::getOrCreateAlbum( Q3PtrList
<MediaItem
> *items
)//uint32_t track_id, const MetaBundle &bundle )
464 LIBMTP_album_t
*album_object
= 0;
465 uint32_t albumid
= 0;
467 QMap
<uint32_t,MtpAlbum
*>::Iterator it
;
468 for( it
= m_idToAlbum
.begin(); it
!= m_idToAlbum
.end(); ++it
)
470 if( it
.data()->album() == items
->first()->bundle()->album() )
472 albumid
= it
.data()->id();
478 debug() << "reusing existing album " << albumid
;
479 album_object
= LIBMTP_Get_Album( m_device
, albumid
);
480 if( album_object
== 0 )
482 debug() << "retrieving album failed.";
486 uint32_t trackCount
= album_object
->no_tracks
;
487 for( MtpMediaItem
*it
= dynamic_cast<MtpMediaItem
*>(items
->first()); it
; it
= dynamic_cast<MtpMediaItem
*>(items
->next()) )
490 for( i
= 0; i
< album_object
->no_tracks
; i
++ )
492 if( album_object
->tracks
[i
] == it
->track()->id() )
500 debug() << "adding track " << it
->track()->id() << " to existing album " << albumid
;
501 album_object
->no_tracks
++;
502 album_object
->tracks
= (uint32_t *)realloc( album_object
->tracks
, album_object
->no_tracks
* sizeof( uint32_t ) );
503 album_object
->tracks
[ ( album_object
->no_tracks
- 1 ) ] = it
->track()->id();
506 if( trackCount
!= album_object
->no_tracks
) // album needs an update
508 ret
= LIBMTP_Update_Album( m_device
, album_object
);
510 debug() << "updating album failed : " << ret
;
515 debug() << "creating new album ";
516 album_object
= LIBMTP_new_album_t();
517 album_object
->name
= qstrdup( items
->first()->bundle()->album().string().toUtf8() );
518 album_object
->tracks
= (uint32_t *) malloc(items
->count() * sizeof(uint32_t));
520 for( MtpMediaItem
*it
= dynamic_cast<MtpMediaItem
*>(items
->first()); it
; it
= dynamic_cast<MtpMediaItem
*>(items
->next()) )
521 album_object
->tracks
[i
++] = it
->track()->id();
522 album_object
->no_tracks
= items
->count();
523 ret
= LIBMTP_Create_New_Album( m_device
, album_object
, 0 );
526 debug() << "creating album failed : " << ret
;
529 m_idToAlbum
[ album_object
->album_id
] = new MtpAlbum( album_object
);
535 * Check (and optionally create) the folder structure to put a
536 * track into. Return the (possibly new) parent folder ID
539 MtpMediaDevice::checkFolderStructure( const MetaBundle
&bundle
, bool create
)
541 QString artist
= bundle
.artist();
542 if( artist
.isEmpty() )
543 artist
= i18n( "Unknown Artist" );
544 if( bundle
.compilation() == MetaBundle::CompilationYes
)
545 artist
= i18n( "Various Artists" );
546 QString album
= bundle
.album();
547 if( album
.isEmpty() )
548 album
= i18n( "Unknown Album" );
549 QString genre
= bundle
.genre();
550 if( genre
.isEmpty() )
551 genre
= i18n( "Unknown Genre" );
552 m_critical_mutex
.lock();
553 uint32_t parent_id
= getDefaultParentId();
554 QStringList folders
= QStringList::split( "/", m_folderStructure
); // use slash as a dir separator
555 QString completePath
;
556 for( QStringList::Iterator it
= folders
.begin(); it
!= folders
.end(); ++it
)
558 if( (*it
).isEmpty() )
560 // substitute %a , %b , %g
561 (*it
).replace( QRegExp( "%a" ), artist
)
562 .replace( QRegExp( "%b" ), album
)
563 .replace( QRegExp( "%g" ), genre
);
564 // check if it exists
565 uint32_t check_folder
= subfolderNameToID( (*it
).toUtf8(), m_folders
, parent_id
);
566 // create if not exists (if requested)
567 if( check_folder
== 0 )
571 check_folder
= createFolder( (*it
).toUtf8() , parent_id
);
572 if( check_folder
== 0 )
574 m_critical_mutex
.unlock();
580 m_critical_mutex
.unlock();
584 completePath
+= (*it
).toUtf8() + '/';
586 parent_id
= check_folder
;
588 m_critical_mutex
.unlock();
589 debug() << "Folder path : " << completePath
;
595 * Create a new mtp folder
598 MtpMediaDevice::createFolder( const char *name
, uint32_t parent_id
)
600 debug() << "Creating new folder '" << name
<< "' as a child of "<< parent_id
;
601 char *name_copy
= qstrdup( name
);
602 uint32_t new_folder_id
= LIBMTP_Create_Folder( m_device
, name_copy
, parent_id
);
604 debug() << "New folder ID: " << new_folder_id
;
605 if( new_folder_id
== 0 )
607 debug() << "Attempt to create folder '" << name
<< "' failed.";
612 return new_folder_id
;
616 * Recursively search the folder list for a matching one under the specified
617 * parent ID and return the child's ID
620 MtpMediaDevice::subfolderNameToID( const char *name
, LIBMTP_folder_t
*folderlist
, uint32_t parent_id
)
624 if( folderlist
== 0 )
627 if( !strcasecmp( name
, folderlist
->name
) && folderlist
->parent_id
== parent_id
)
628 return folderlist
->folder_id
;
630 if( ( i
= ( subfolderNameToID( name
, folderlist
->child
, parent_id
) ) ) )
632 if( ( i
= ( subfolderNameToID( name
, folderlist
->sibling
, parent_id
) ) ) )
639 * Recursively search the folder list for a matching one
643 MtpMediaDevice::folderNameToID( char *name
, LIBMTP_folder_t
*folderlist
)
647 if( folderlist
== 0 )
650 if( !strcasecmp( name
, folderlist
->name
) )
651 return folderlist
->folder_id
;
653 if( ( i
= ( folderNameToID( name
, folderlist
->child
) ) ) )
655 if( ( i
= ( folderNameToID( name
, folderlist
->sibling
) ) ) )
662 * Get a list of selected items, download them to a temporary location and
666 MtpMediaDevice::downloadSelectedItemsToCollection()
668 Q3PtrList
<MediaItem
> items
;
669 m_view
->getSelectedLeaves( 0, &items
);
671 KTempDir
tempdir( QString() );
672 tempdir
.setAutoDelete( true );
674 QString genericError
= i18n( "Could not copy track from device." );
677 total
= items
.count();
683 setProgress( progress
, total
);
684 for( MtpMediaItem
*it
= dynamic_cast<MtpMediaItem
*>(items
.first()); it
&& !(m_canceled
); it
= dynamic_cast<MtpMediaItem
*>(items
.next()) )
686 if( it
->type() == MediaItem::TRACK
)
688 QString filename
= tempdir
.name() + it
->bundle()->filename();
689 int ret
= LIBMTP_Get_Track_To_File(
690 m_device
, it
->track()->id(), filename
.toUtf8(),
691 progressCallback
, this
695 debug() << "Get Track failed: " << ret
;
696 Amarok::StatusBar::instance()->shortLongMessage(
698 i18n( "Could not copy track from device." ),
699 KDE::StatusBar::Error
706 setProgress( progress
);
712 setProgress( progress
, total
);
716 //PORT 2.0 CollectionView::instance()->organizeFiles( urls, i18n( "Move Files To Collection" ), false );
721 * Write any pending changes to the device, such as database changes
724 MtpMediaDevice::synchronizeDevice()
726 updateAlbumArt( m_newTracks
);
727 m_newTracks
->clear();
732 * Create a new playlist
735 // *MtpMediaDevice::newPlaylist( const QString &name, MediaItem *parent, Q3PtrList<MediaItem> items )
738 // MtpMediaItem *item = new MtpMediaItem( parent, this );
739 // item->setType( MediaItem::PLAYLIST );
740 // item->setText( 0, name );
741 // item->setPlaylist( new MtpPlaylist() );
743 // addToPlaylist( item, 0, items );
745 // if( ! isTransferring() )
746 // m_view->rename( item, 0 );
752 * Add an item to a playlist
755 // MtpMediaDevice::addToPlaylist( MediaItem *mlist, MediaItem *after, Q3PtrList<MediaItem> items )
758 // MtpMediaItem *list = dynamic_cast<MtpMediaItem *>( mlist );
766 // order = after->m_order + 1;
767 // it = dynamic_cast<MtpMediaItem*>(after->nextSibling());
772 // it = dynamic_cast<MtpMediaItem*>( list->firstChild() );
775 // for( ; it; it = dynamic_cast<MtpMediaItem *>( it->nextSibling() ) )
777 // it->m_order += items.count();
780 // for( MtpMediaItem *it = dynamic_cast<MtpMediaItem *>(items.first() );
782 // it = dynamic_cast<MtpMediaItem *>( items.next() ) )
784 // if( !it->track() )
787 // MtpMediaItem *add;
788 // if( it->parent() == list )
793 // it->moveItem(after);
797 // list->takeItem(it);
798 // list->insertItem(it);
805 // add = new MtpMediaItem( list, after );
809 // add = new MtpMediaItem( list, this );
814 // add->setType( MediaItem::PLAYLISTITEM );
815 // add->setTrack( it->track() );
816 // add->setBundle( new MetaBundle( *(it->bundle()) ) );
817 // add->m_device = this;
818 // add->setText( 0, it->bundle()->artist() + " - " + it->bundle()->title() );
819 // add->m_order = order;
823 // // make numbering consecutive
825 // for( MtpMediaItem *it = dynamic_cast<MtpMediaItem *>( list->firstChild() );
827 // it = dynamic_cast<MtpMediaItem *>( it->nextSibling() ) )
833 // playlistFromItem( list );
837 * When a playlist has been renamed, we must save it
840 // MtpMediaDevice::playlistRenamed( Q3ListViewItem *item, const QString &, int ) // SLOT
843 // MtpMediaItem *playlist = static_cast<MtpMediaItem*>( item );
844 // if( playlist->type() == MediaItem::PLAYLIST )
845 // playlistFromItem( playlist );
852 // MtpMediaDevice::playlistFromItem( MtpMediaItem *item )
854 // if( item->childCount() == 0 )
856 // m_critical_mutex.lock();
857 // LIBMTP_playlist_t *metadata = LIBMTP_new_playlist_t();
858 // metadata->name = qstrdup( item->text( 0 ).toUtf8() );
859 // const int trackCount = item->childCount();
860 // if (trackCount > 0) {
861 // uint32_t *tracks = ( uint32_t* )malloc( sizeof( uint32_t ) * trackCount );
863 // for( MtpMediaItem *it = dynamic_cast<MtpMediaItem *>(item->firstChild());
865 // it = dynamic_cast<MtpMediaItem *>(it->nextSibling()) )
867 // tracks[i] = it->track()->id();
870 // metadata->tracks = tracks;
871 // metadata->no_tracks = i;
873 // debug() << "no tracks available for playlist " << metadata->name
875 // metadata->no_tracks = 0;
877 // QString genericError = i18n( "Could not save playlist." );
879 // uint32_t *tracks = ( uint32_t* )malloc( sizeof( uint32_t ) * item->childCount() );
881 // for( MtpMediaItem *it = dynamic_cast<MtpMediaItem *>(item->firstChild());
883 // it = dynamic_cast<MtpMediaItem *>(it->nextSibling()) )
885 // tracks[i] = it->track()->id();
888 // metadata->tracks = tracks;
889 // metadata->no_tracks = i;
891 // QString genericError = i18n( "Could not save playlist." );
893 // if( item->playlist()->id() == 0 )
895 // debug() << "creating new playlist : " << metadata->name;
896 // int ret = LIBMTP_Create_New_Playlist( m_device, metadata, 0 );
899 // item->playlist()->setId( metadata->playlist_id );
900 // debug() << "playlist saved : " << metadata->playlist_id;
904 // Amarok::StatusBar::instance()->shortLongMessage(
906 // i18n( "Could not create new playlist on device." ),
907 // KDE::StatusBar::Error
913 // metadata->playlist_id = item->playlist()->id();
914 // debug() << "updating playlist : " << metadata->name;
915 // int ret = LIBMTP_Update_Playlist( m_device, metadata );
918 // Amarok::StatusBar::instance()->shortLongMessage(
920 // i18n( "Could not update playlist on device." ),
921 // KDE::StatusBar::Error
925 // m_critical_mutex.unlock();
929 * Recursively remove MediaItem from the device and media view
932 MtpMediaDevice::deleteItemFromDevice(MediaItem
* item
, int flags
)
936 if( isCanceled() || !item
)
942 switch( item
->type() )
944 case MediaItem::PLAYLIST
:
945 case MediaItem::TRACK
:
950 int res
= deleteObject( dynamic_cast<MtpMediaItem
*> ( item
) );
951 if( res
>=0 && result
>= 0 )
957 case MediaItem::PLAYLISTITEM
:
962 MtpMediaItem
*parent
= dynamic_cast<MtpMediaItem
*> ( item
->parent() );
963 if( parent
&& parent
->type() == MediaItem::PLAYLIST
) {
965 playlistFromItem( parent
);
969 case MediaItem::ALBUM
:
970 case MediaItem::ARTIST
:
971 // Recurse through the lists
977 for( MediaItem
*it
= dynamic_cast<MediaItem
*>( item
->firstChild() ); it
; it
= next
)
979 next
= dynamic_cast<MediaItem
*>( it
->nextSibling() );
980 int res
= deleteItemFromDevice( it
, flags
);
981 if( res
>= 0 && result
>= 0 )
988 delete dynamic_cast<MediaItem
*>( item
);
997 * Actually delete a track or playlist
1000 MtpMediaDevice::deleteObject( MtpMediaItem
*deleteItem
)
1004 u_int32_t object_id
;
1005 if( deleteItem
->type() == MediaItem::PLAYLIST
)
1006 object_id
= deleteItem
->playlist()->id();
1008 object_id
= deleteItem
->track()->id();
1010 QString genericError
= i18n( "Could not delete item" );
1012 debug() << "delete this id : " << object_id
;
1014 m_critical_mutex
.lock();
1015 int status
= LIBMTP_Delete_Object( m_device
, object_id
);
1016 m_critical_mutex
.unlock();
1020 debug() << "delete object failed";
1021 Amarok::StatusBar::instance()->shortLongMessage(
1023 i18n( "Delete failed" ),
1024 KDE::StatusBar::Error
1028 debug() << "object deleted";
1030 // clear cached filename
1031 if( deleteItem
->type() == MediaItem::TRACK
)
1032 m_fileNameToItem
.remove( QString( "%1/%2" ).arg( deleteItem
->track()->folderId() ).arg( deleteItem
->bundle()->filename() ) );
1033 // remove from the media view
1035 kapp
->processEvents( 100 );
1041 * Update local cache of mtp folders
1044 MtpMediaDevice::updateFolders( void )
1046 LIBMTP_destroy_folder_t( m_folders
);
1048 m_folders
= LIBMTP_Get_Folder_List( m_device
);
1052 * Set cancellation of an operation
1055 MtpMediaDevice::cancelTransfer()
1061 * Connect to device, and populate m_view with MediaItems
1064 MtpMediaDevice::openDevice( bool silent
)
1073 QString genericError
= i18n( "Could not connect to MTP Device" );
1075 m_critical_mutex
.lock();
1077 m_device
= LIBMTP_Get_First_Device();
1078 m_critical_mutex
.unlock();
1079 if( m_device
== 0 ) {
1080 debug() << "No devices.";
1081 Amarok::StatusBar::instance()->shortLongMessage(
1083 i18n( "MTP device could not be opened" ),
1084 KDE::StatusBar::Error
1090 QString modelname
= QString( LIBMTP_Get_Modelname( m_device
) );
1091 QString ownername
= QString( LIBMTP_Get_Friendlyname( m_device
) );
1093 if(! ownername
.isEmpty() )
1094 m_name
+= " (" + ownername
+ ')';
1096 m_default_parent_folder
= m_device
->default_music_folder
;
1097 debug() << "setting default parent : " << m_default_parent_folder
;
1099 MtpMediaDevice::readMtpMusic();
1101 m_critical_mutex
.lock();
1102 m_folders
= LIBMTP_Get_Folder_List( m_device
);
1103 uint16_t *filetypes
;
1104 uint16_t filetypes_len
;
1105 int ret
= LIBMTP_Get_Supported_Filetypes( m_device
, &filetypes
, &filetypes_len
);
1109 for( i
= 0; i
< filetypes_len
; i
++ )
1110 m_supportedFiles
<< mtpFileTypes
[ filetypes
[ i
] ];
1112 // find supported image types (for album art).
1113 if( m_supportedFiles
.findIndex( "jpg" ) )
1115 else if( m_supportedFiles
.findIndex( "png" ) )
1117 else if( m_supportedFiles
.findIndex( "gif" ) )
1120 m_critical_mutex
.unlock();
1126 * Start the view (add default folders such as for playlists)
1129 MtpMediaDevice::initView()
1131 if( ! isConnected() )
1133 m_playlistItem
= new MtpMediaItem( m_view
, this );
1134 m_playlistItem
->setText( 0, i18n("Playlists") );
1135 m_playlistItem
->setType( MediaItem::PLAYLISTSROOT
);
1136 m_playlistItem
->m_order
= -1;
1140 * Wrap up any loose ends and close the device
1143 MtpMediaDevice::closeDevice()
1147 // clear folder structure
1148 if( m_folders
!= 0 )
1150 m_critical_mutex
.lock();
1151 LIBMTP_destroy_folder_t( m_folders
);
1152 m_critical_mutex
.unlock();
1154 debug() << "Folders destroyed";
1160 m_critical_mutex
.lock();
1161 LIBMTP_Release_Device( m_device
);
1162 m_critical_mutex
.unlock();
1164 debug() << "Device released";
1167 // clear the cached mappings
1168 m_idToAlbum
.clear();
1169 m_idToTrack
.clear();
1170 m_fileNameToItem
.clear();
1172 // clean up the view
1179 * Get the capacity and freespace available on the device, in KB
1182 MtpMediaDevice::getCapacity( KIO::filesize_t
*total
, KIO::filesize_t
*available
)
1184 if( !isConnected() )
1187 // TODO : Follow the links so we sum up all the device's storage.
1188 *total
= m_device
->storage
->MaxCapacity
;
1189 *available
= m_device
->storage
->FreeSpaceInBytes
;
1194 * Get custom information about the device via MTP
1197 MtpMediaDevice::customClicked()
1199 QString Information
;
1202 QString batteryLevel
;
1204 QString supportedFiles
;
1206 uint8_t maxbattlevel
;
1207 uint8_t currbattlevel
;
1211 m_critical_mutex
.lock();
1212 LIBMTP_Get_Batterylevel( m_device
, &maxbattlevel
, &currbattlevel
);
1213 LIBMTP_Get_Secure_Time( m_device
, §ime
);
1214 m_critical_mutex
.unlock();
1216 batteryLevel
= i18n("Battery level: ")
1217 + QString::number( (int) ( (float) currbattlevel
/ (float) maxbattlevel
* 100.0 ) )
1219 secureTime
= i18n("Secure time: ") + sectime
;
1220 supportedFiles
= i18n("Supported file types: ")
1221 + m_supportedFiles
.join( ", " );
1223 Information
= ( i18n( "Player Information for " )
1224 + m_name
+ '\n' + batteryLevel
1225 + '\n' + secureTime
+ '\n'
1231 Information
= i18n( "Player not connected" );
1234 KMessageBox::information( 0, Information
, i18n( "Device information" ) );
1241 *MtpMediaDevice::current_device()
1247 * We use a 0 device to show a disconnected device.
1248 * This sets the device to that.
1251 MtpMediaDevice::setDisconnected()
1257 * Handle clicking of the right mouse button
1260 MtpMediaDevice::rmbPressed( Q3ListViewItem
*qitem
, const QPoint
&point
, int )
1263 enum Actions
{RENAME
, DOWNLOAD
, DELETE
, MAKE_PLAYLIST
, UPDATE_ALBUM_ART
};
1265 MtpMediaItem
*item
= static_cast<MtpMediaItem
*>( qitem
);
1268 KMenu
menu( m_view
);
1269 switch( item
->type() )
1271 case MediaItem::ARTIST
:
1272 case MediaItem::ALBUM
:
1273 case MediaItem::TRACK
:
1274 menu
.insertItem( SmallIconSet( Amarok::icon( "collection" ) ), i18n("&Copy Files to Collection..."), DOWNLOAD
);
1275 menu
.insertItem( SmallIconSet( Amarok::icon( "playlist" ) ), i18n( "Make Media Device Playlist" ), MAKE_PLAYLIST
);
1276 menu
.insertItem( SmallIconSet( Amarok::icon( "covermanager" ) ), i18n( "Refresh Cover Images" ), UPDATE_ALBUM_ART
);
1278 case MediaItem::PLAYLIST
:
1279 menu
.insertItem( SmallIconSet( Amarok::icon( "edit" ) ), i18n( "Rename" ), RENAME
);
1285 menu
.insertItem( SmallIconSet( Amarok::icon( "remove" ) ), i18n( "Delete from device" ), DELETE
);
1287 int id
= menu
.exec( point
);
1292 Q3PtrList
<MediaItem
> items
;
1293 m_view
->getSelectedLeaves( 0, &items
);
1294 QString name
= i18n( "New Playlist" );
1295 newPlaylist( name
, m_playlistItem
, items
);
1299 MediaDevice::deleteFromDevice();
1302 if( item
->type() == MediaItem::PLAYLIST
&& ! isTransferring() )
1304 m_view
->rename( item
, 0 );
1308 downloadSelectedItemsToCollection();
1310 case UPDATE_ALBUM_ART
:
1312 Q3PtrList
<MediaItem
> *items
= new Q3PtrList
<MediaItem
>;
1313 m_view
->getSelectedLeaves( 0, items
);
1315 if( items
->count() > 100 )
1317 int button
= KMessageBox::warningContinueCancel( m_parent
,
1318 i18np( "<p>You are updating cover art for 1 track. This may take some time.",
1319 "<p>You are updating cover art for %n tracks. This may take some time.",
1324 if( button
!= KMessageBox::Continue
)
1327 updateAlbumArt( items
);
1336 * Add gui elements to the device configuration
1339 MtpMediaDevice::addConfigElements( QWidget
*parent
)
1342 m_folderLabel
= new QLabel( parent
);
1343 m_folderLabel
->setText( i18n( "Folder structure:" ) );
1345 m_folderStructureBox
= new QLineEdit( parent
);
1346 m_folderStructureBox
->setText( m_folderStructure
);
1347 m_folderStructureBox
->setToolTip(
1348 i18n( "Files copied to the device will be placed in this folder." ) + '\n'
1349 + i18n( "/ is used as folder separator." ) + '\n'
1350 + i18n( "%a will be replaced with the artist name, ")
1351 + i18n( "%b with the album name," ) + '\n'
1352 + i18n( "%g with the genre.") + '\n'
1353 + i18n( "An empty path means the files will be placed unsorted in the default music folder." ) );
1357 * Remove gui elements from the device configuration
1360 MtpMediaDevice::removeConfigElements( QWidget
*parent
)
1364 delete m_folderStructureBox
;
1365 m_folderStructureBox
= 0;
1367 delete m_folderLabel
;
1372 * Save changed config after dialog commit
1375 MtpMediaDevice::applyConfig()
1377 m_folderStructure
= m_folderStructureBox
->text();
1378 setConfigString( "FolderStructure", m_folderStructure
);
1382 * Load config from the amarokrc file
1385 MtpMediaDevice::loadConfig()
1387 m_folderStructure
= configString( "FolderStructure","%a - %b" );
1391 * Add a track to the current Collection
1394 *MtpMediaDevice::addTrackToCollection( MtpTrack
*track
, MtpMediaItem
*item
)
1396 QString artistName
= track
->bundle()->artist();
1398 MtpMediaItem
*artist
= dynamic_cast<MtpMediaItem
*>( m_view
->findItem( artistName
, 0 ) );
1401 artist
= new MtpMediaItem(m_view
);
1402 artist
->m_device
= this;
1403 artist
->setText( 0, artistName
);
1404 artist
->setType( MediaItem::ARTIST
);
1407 QString albumName
= track
->bundle()->album();
1408 MtpMediaItem
*album
= dynamic_cast<MtpMediaItem
*>( artist
->findItem( albumName
) );
1411 album
= new MtpMediaItem( artist
);
1412 album
->setText( 0, albumName
);
1413 album
->setType( MediaItem::ALBUM
);
1414 album
->m_device
= this;
1418 album
->insertItem( item
);
1421 item
= new MtpMediaItem( album
);
1422 item
->m_device
= this;
1423 QString titleName
= track
->bundle()->title();
1424 item
->setTrack( track
);
1425 item
->m_order
= track
->bundle()->track();
1426 item
->setText( 0, titleName
);
1427 item
->setType( MediaItem::TRACK
);
1428 item
->setBundle( track
->bundle() );
1429 item
->track()->setId( track
->id() );
1430 m_fileNameToItem
[ QString( "%1/%2" ).arg( track
->folderId() ).arg( track
->bundle()->filename() ) ] = item
;
1431 m_idToTrack
[ track
->id() ] = track
;
1437 * Get tracks and add them to the listview
1440 MtpMediaDevice::readMtpMusic()
1446 m_critical_mutex
.lock();
1448 QString genericError
= i18n( "Could not get music from MTP Device" );
1452 setProgress( progress
, total
); // we don't know how many tracks. fake progress bar.
1454 kapp
->processEvents( 100 );
1456 LIBMTP_track_t
*tracks
= LIBMTP_Get_Tracklisting_With_Callback( m_device
, progressCallback
, this );
1458 debug() << "Got tracks from device";
1462 debug() << "0 tracks returned. Empty device...";
1466 LIBMTP_track_t
*tmp
= tracks
;
1468 // spin through once to determine size of the list
1469 while( tracks
!= 0 )
1471 tracks
= tracks
->next
;
1474 setProgress( progress
, total
);
1476 // now process the tracks
1477 while( tracks
!= 0 )
1479 MtpTrack
*mtp_track
= new MtpTrack( tracks
);
1480 mtp_track
->readMetaData( tracks
);
1481 addTrackToView( mtp_track
);
1483 tracks
= tracks
->next
;
1484 LIBMTP_destroy_track_t( tmp
);
1486 setProgress( progress
);
1487 if( progress
% 50 == 0 )
1488 kapp
->processEvents( 100 );
1495 setProgress( total
);
1498 m_critical_mutex
.unlock();
1504 * Populate playlists
1507 MtpMediaDevice::readPlaylists()
1509 LIBMTP_playlist_t
*playlists
= LIBMTP_Get_Playlist_List( m_device
);
1511 if( playlists
!= 0 )
1513 LIBMTP_playlist_t
*tmp
;
1514 while( playlists
!= 0 )
1516 MtpMediaItem
*playlist
= new MtpMediaItem( m_playlistItem
, this );
1517 playlist
->setText( 0, QString::fromUtf8( playlists
->name
) );
1518 playlist
->setType( MediaItem::PLAYLIST
);
1519 playlist
->setPlaylist( new MtpPlaylist() );
1520 playlist
->playlist()->setId( playlists
->playlist_id
);
1522 for( i
= 0; i
< playlists
->no_tracks
; i
++ )
1524 MtpTrack
*track
= m_idToTrack
[ playlists
->tracks
[i
] ];
1525 if( track
== 0 ) // skip invalid playlist entries
1527 MtpMediaItem
*item
= new MtpMediaItem( playlist
);
1528 item
->setText( 0, track
->bundle()->artist() + " - " + track
->bundle()->title() );
1529 item
->setType( MediaItem::PLAYLISTITEM
);
1530 item
->setBundle( track
->bundle() );
1531 item
->setTrack( track
);
1533 item
->m_device
= this;
1536 playlists
= playlists
->next
;
1537 LIBMTP_destroy_playlist_t( tmp
);
1538 kapp
->processEvents( 50 );
1545 * Read existing albums
1548 MtpMediaDevice::readAlbums()
1550 LIBMTP_album_t
*albums
= LIBMTP_Get_Album_List( m_device
);
1554 LIBMTP_album_t
*tmp
;
1555 while( albums
!= 0 )
1557 m_idToAlbum
[ albums
->album_id
] = new MtpAlbum( albums
);
1559 albums
= albums
->next
;
1560 LIBMTP_destroy_album_t( tmp
);
1561 kapp
->processEvents( 50 );
1567 * Clear the current Collection
1570 MtpMediaDevice::clearItems()
1578 MtpTrack::MtpTrack( LIBMTP_track_t
*track
)
1580 m_id
= track
->item_id
;
1584 * Read track properties from the device and set it on the track
1587 MtpTrack::readMetaData( LIBMTP_track_t
*track
)
1589 MetaBundle
*bundle
= new MetaBundle();
1591 if( track
->genre
!= 0 )
1592 bundle
->setGenre( AtomicString( QString::fromUtf8( track
->genre
) ) );
1593 if( track
->artist
!= 0 )
1594 bundle
->setArtist( AtomicString( QString::fromUtf8( track
->artist
) ) );
1595 if( track
->album
!= 0 )
1596 bundle
->setAlbum( AtomicString( QString::fromUtf8( track
->album
) ) );
1597 if( track
->title
!= 0 )
1598 bundle
->setTitle( AtomicString( QString::fromUtf8( track
->title
) ) );
1599 if( track
->filename
!= 0 )
1600 bundle
->setPath( AtomicString( QString::fromUtf8( track
->filename
) ) );
1602 // translate codecs to file types
1603 if( track
->filetype
== LIBMTP_FILETYPE_MP3
)
1604 bundle
->setFileType( MetaBundle::mp3
);
1605 else if( track
->filetype
== LIBMTP_FILETYPE_WMA
)
1606 bundle
->setFileType( MetaBundle::wma
);
1607 else if( track
->filetype
== LIBMTP_FILETYPE_OGG
)
1608 bundle
->setFileType( MetaBundle::ogg
);
1610 bundle
->setFileType( MetaBundle::other
);
1612 if( track
->date
!= 0 )
1613 bundle
->setYear( QString( QString::fromUtf8( track
->date
) ).mid( 0, 4 ).toUInt() );
1614 if( track
->tracknumber
> 0 )
1615 bundle
->setTrack( track
->tracknumber
);
1616 if( track
->duration
> 0 )
1617 bundle
->setLength( track
->duration
/ 1000 ); // Divide by 1000 since this is in milliseconds
1619 this->setFolderId( track
->parent_id
);
1621 this->setBundle( *bundle
);
1627 MtpAlbum::MtpAlbum( LIBMTP_album_t
*album
)
1629 m_id
= album
->album_id
;
1630 m_album
= QString::fromUtf8( album
->name
);