Port forward 703403
[amarok.git] / src / mediadevice / mtp / mtpmediadevice.cpp
blob6153e19fe7da1c4624be3d9eb301311d01770f24
1 /***************************************************************************
2 * copyright : (C) 2006 Andy Kelk <andy@mopoke.co.uk> *
3 ***************************************************************************/
5 /***************************************************************************
6 * *
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. *
11 * *
12 ***************************************************************************/
14 /**
15 * Based on njb mediadevice with some code hints from the libmtp
16 * example tools
19 /**
20 * MTP media device
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 //Added by qt3to4:
30 #include <QLabel>
31 #include <Q3PtrList>
33 AMAROK_EXPORT_PLUGIN( MtpMediaDevice )
35 // Amarok
36 #include <debug.h>
37 #include <metabundle.h>
38 #include <statusbar/statusbar.h>
39 #include <statusbar/popupMessage.h>
41 // KDE
42 #include <kapplication.h>
43 #include <kiconloader.h>
44 #include <kmenu.h>
45 #include <kmessagebox.h>
47 #include <ktempdir.h>
49 // Qt
50 #include <QDir>
51 #include <q3listview.h>
52 #include <QToolTip>
53 #include <QLineEdit>
54 #include <QRegExp>
55 #include <QBuffer>
56 #include <qmap.h>
58 /**
59 * MtpMediaDevice Class
62 MtpMediaDevice::MtpMediaDevice() : MediaDevice()
64 m_name = i18n("MTP Media Device");
65 m_device = 0;
66 m_folders = 0;
67 m_playlistItem = 0;
68 setDisconnected();
69 m_hasMountPoint = false;
70 m_syncStats = false;
71 m_transcode = false;
72 m_transcodeAlways = false;
73 m_transcodeRemove = false;
74 m_configure = false;
75 m_customButton = true;
76 m_transfer = 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>;
116 void
117 MtpMediaDevice::init( MediaBrowser *parent )
119 MediaDevice::init( parent );
122 bool
123 MtpMediaDevice::isConnected()
125 return !( m_device == 0 );
129 * File types that we support
131 QStringList
132 MtpMediaDevice::supportedFiletypes()
134 return m_supportedFiles;
139 MtpMediaDevice::progressCallback( uint64_t const sent, uint64_t const total, void const * const data )
141 Q_UNUSED( sent );
142 Q_UNUSED( total );
144 kapp->processEvents( 100 );
146 MtpMediaDevice *dev = (MtpMediaDevice*)(data);
148 if( dev->isCanceled() )
150 debug() << "Canceling transfer operation";
151 dev->setCanceled( true );
152 return 1;
155 return 0;
159 * Copy a track to the device
161 MediaItem
162 *MtpMediaDevice::copyTrackToDevice( const MetaBundle &bundle )
164 DEBUG_BLOCK
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;
187 else
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;
203 else
205 debug() << "We don't support the extension ." << extension;
206 Amarok::StatusBar::instance()->shortLongMessage(
207 genericError,
208 i18n( "Cannot determine a valid file type" ),
209 KDE::StatusBar::Error
211 return 0;
215 if( bundle.title().isEmpty() )
217 trackmeta->title = qstrdup( i18n( "Unknown title" ).toUtf8() );
219 else
221 trackmeta->title = qstrdup( bundle.title().toUtf8() );
224 if( bundle.album().isEmpty() )
226 trackmeta->album = qstrdup( i18n( "Unknown album" ).toUtf8() );
228 else
230 trackmeta->album = qstrdup( bundle.album().string().toUtf8() );
233 if( bundle.artist().isEmpty() )
235 trackmeta->artist = qstrdup( i18n( "Unknown artist" ).toUtf8() );
237 else
239 trackmeta->artist = qstrdup( bundle.artist().string().toUtf8() );
242 if( bundle.genre().isEmpty() )
244 trackmeta->genre = qstrdup( i18n( "Unknown genre" ).toUtf8() );
246 else
248 trackmeta->genre = qstrdup( bundle.genre().string().toUtf8() );
251 if( bundle.year() > 0 )
253 QString date;
254 QTextOStream( &date ) << bundle.year() << "0101T0000.0";
255 trackmeta->date = qstrdup( date.toUtf8() );
257 else
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 );
282 if( parent_id == 0 )
284 debug() << "Couldn't create new parent (" << m_folderStructure << ")";
285 Amarok::StatusBar::instance()->shortLongMessage(
286 genericError,
287 i18n( "Cannot create parent folder. Check your structure." ),
288 KDE::StatusBar::Error
290 return 0;
293 else
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();
307 if( ret < 0 )
309 debug() << "Could not write file " << ret;
310 Amarok::StatusBar::instance()->shortLongMessage(
311 genericError,
312 i18n( "File write failed" ),
313 KDE::StatusBar::Error
315 return 0;
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 );
330 return 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.
337 void
338 MtpMediaDevice::sendAlbumArt( Q3PtrList<MediaItem> *items )
340 QString image;
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 );
346 if( imagedata == 0 )
348 debug() << "Cannot generate a supported image format";
349 return;
351 if( imagedata->size() )
353 m_critical_mutex.lock();
354 LIBMTP_album_t *album_object = getOrCreateAlbum( items );
355 if( album_object )
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 );
362 if( ret != 0 )
364 debug() << "image send failed : " << ret;
367 m_critical_mutex.unlock();
372 uint32_t
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 );
386 if( !parent_id )
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
392 else
394 debug() << "No folders found. Going to use top level.";
396 return parent_id;
400 * Takes path to an existing cover image and converts it to a format
401 * supported on the device
403 QByteArray
404 *MtpMediaDevice::getSupportedImage( QString path )
406 if( m_format == 0 )
407 return 0;
409 debug() << "Will convert image to " << m_format;
411 // open image
412 const QImage original( path );
414 // save as new image
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() ) )
421 buffer.close();
422 return newimage;
424 return 0;
428 * Update cover art for a number of tracks
430 void
431 MtpMediaDevice::updateAlbumArt( Q3PtrList<MediaItem> *items )
433 DEBUG_BLOCK
435 if( m_format == 0 ) // no supported image types. Don't even bother.
436 return;
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()) )
445 // build album list
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!";
455 int i = 0;
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() );
462 setProgress( ++i );
463 if( i % 20 == 0 )
464 kapp->processEvents( 100 );
466 hideProgress();
470 * Retrieve existing or create new album object.
472 LIBMTP_album_t
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;
477 int ret;
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();
484 break;
487 if( albumid )
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.";
494 return 0;
496 uint32_t i;
497 uint32_t trackCount = album_object->no_tracks;
498 for( MtpMediaItem *it = dynamic_cast<MtpMediaItem*>(items->first()); it; it = dynamic_cast<MtpMediaItem*>(items->next()) )
500 bool exists = false;
501 for( i = 0; i < album_object->no_tracks; i++ )
503 if( album_object->tracks[i] == it->track()->id() )
505 exists = true;
506 break;
509 if( ! exists )
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 );
520 if( ret != 0 )
521 debug() << "updating album failed : " << ret;
524 else
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));
530 int i = 0;
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 );
535 if( ret != 0 )
537 debug() << "creating album failed : " << ret;
538 return 0;
540 m_idToAlbum[ album_object->album_id ] = new MtpAlbum( album_object );
542 return album_object;
546 * Check (and optionally create) the folder structure to put a
547 * track into. Return the (possibly new) parent folder ID
549 uint32_t
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() )
570 continue;
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 )
580 if( create )
582 check_folder = createFolder( (*it).toUtf8() , parent_id );
583 if( check_folder == 0 )
585 m_critical_mutex.unlock();
586 return 0;
589 else
591 m_critical_mutex.unlock();
592 return 0;
595 completePath += (*it).toUtf8() + '/';
596 // set new parent
597 parent_id = check_folder;
599 m_critical_mutex.unlock();
600 debug() << "Folder path : " << completePath;
601 // return parent
602 return parent_id;
606 * Create a new mtp folder
608 uint32_t
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 );
614 delete(name_copy);
615 debug() << "New folder ID: " << new_folder_id;
616 if( new_folder_id == 0 )
618 debug() << "Attempt to create folder '" << name << "' failed.";
619 return 0;
621 updateFolders();
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
630 uint32_t
631 MtpMediaDevice::subfolderNameToID( const char *name, LIBMTP_folder_t *folderlist, uint32_t parent_id )
633 uint32_t i;
635 if( folderlist == 0 )
636 return 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 ) ) ) )
642 return i;
643 if( ( i = ( subfolderNameToID( name, folderlist->sibling, parent_id ) ) ) )
644 return i;
646 return 0;
650 * Recursively search the folder list for a matching one
651 * and return its ID
653 uint32_t
654 MtpMediaDevice::folderNameToID( char *name, LIBMTP_folder_t *folderlist )
656 uint32_t i;
658 if( folderlist == 0 )
659 return 0;
661 if( !strcasecmp( name, folderlist->name ) )
662 return folderlist->folder_id;
664 if( ( i = ( folderNameToID( name, folderlist->child ) ) ) )
665 return i;
666 if( ( i = ( folderNameToID( name, folderlist->sibling ) ) ) )
667 return i;
669 return 0;
673 * Get a list of selected items, download them to a temporary location and
674 * organize.
677 MtpMediaDevice::downloadSelectedItemsToCollection()
679 Q3PtrList<MediaItem> items;
680 m_view->getSelectedLeaves( 0, &items );
682 KTempDir tempdir( QString() );
683 tempdir.setAutoDelete( true );
684 KUrl::List urls;
685 QString genericError = i18n( "Could not copy track from device." );
687 int total,progress;
688 total = items.count();
689 progress = 0;
691 if( total == 0 )
692 return 0;
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
704 if( ret != 0 )
706 debug() << "Get Track failed: " << ret;
707 Amarok::StatusBar::instance()->shortLongMessage(
708 genericError,
709 i18n( "Could not copy track from device." ),
710 KDE::StatusBar::Error
713 else
715 urls << filename;
716 progress++;
717 setProgress( progress );
720 else
722 total--;
723 setProgress( progress, total );
726 hideProgress();
727 //PORT 2.0 CollectionView::instance()->organizeFiles( urls, i18n( "Move Files To Collection" ), false );
728 return 0;
732 * Write any pending changes to the device, such as database changes
734 void
735 MtpMediaDevice::synchronizeDevice()
737 updateAlbumArt( m_newTracks );
738 m_newTracks->clear();
739 return;
743 * Find an existing track
745 MediaItem
746 *MtpMediaDevice::trackExists( const MetaBundle &bundle )
748 MediaItem *artist = dynamic_cast<MediaItem *>( m_view->findItem( bundle.artist(), 0 ) );
749 if( artist )
751 MediaItem *album = dynamic_cast<MediaItem *>( artist->findItem( bundle.album() ) );
752 if( album )
754 MediaItem *track = dynamic_cast<MediaItem *>( album->findItem( bundle.title() ) );
755 if( track )
756 return track;
759 uint32_t folderId = checkFolderStructure( bundle, false );
760 MediaItem *file = m_fileNameToItem[ QString( "%1/%2" ).arg( folderId ).arg( bundle.filename() ) ];
761 if( file != 0 )
762 return file;
763 return 0;
767 * Create a new playlist
769 MtpMediaItem
770 *MtpMediaDevice::newPlaylist( const QString &name, MediaItem *parent, Q3PtrList<MediaItem> items )
772 DEBUG_BLOCK
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 );
783 return item;
787 * Add an item to a playlist
789 void
790 MtpMediaDevice::addToPlaylist( MediaItem *mlist, MediaItem *after, Q3PtrList<MediaItem> items )
792 DEBUG_BLOCK
793 MtpMediaItem *list = dynamic_cast<MtpMediaItem *>( mlist );
794 if( !list )
795 return;
797 int order;
798 MtpMediaItem *it;
799 if( after )
801 order = after->m_order + 1;
802 it = dynamic_cast<MtpMediaItem*>(after->nextSibling());
804 else
806 order = 0;
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() ) )
819 if( !it->track() )
820 continue;
822 MtpMediaItem *add;
823 if( it->parent() == list )
825 add = it;
826 if( after )
828 it->moveItem(after);
830 else
832 list->takeItem(it);
833 list->insertItem(it);
836 else
838 if( after )
840 add = new MtpMediaItem( list, after );
842 else
844 add = new MtpMediaItem( list, this );
847 after = add;
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;
855 order++;
858 // make numbering consecutive
859 int i = 0;
860 for( MtpMediaItem *it = dynamic_cast<MtpMediaItem *>( list->firstChild() );
862 it = dynamic_cast<MtpMediaItem *>( it->nextSibling() ) )
864 it->m_order = i;
865 i++;
868 playlistFromItem( list );
872 * When a playlist has been renamed, we must save it
874 void
875 MtpMediaDevice::playlistRenamed( Q3ListViewItem *item, const QString &, int ) // SLOT
877 DEBUG_BLOCK
878 MtpMediaItem *playlist = static_cast<MtpMediaItem*>( item );
879 if( playlist->type() == MediaItem::PLAYLIST )
880 playlistFromItem( playlist );
884 * Save a playlist
886 void
887 MtpMediaDevice::playlistFromItem( MtpMediaItem *item )
889 if( item->childCount() == 0 )
890 return;
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 );
897 uint32_t i = 0;
898 for( MtpMediaItem *it = dynamic_cast<MtpMediaItem *>(item->firstChild());
900 it = dynamic_cast<MtpMediaItem *>(it->nextSibling()) )
902 tracks[i] = it->track()->id();
903 i++;
905 metadata->tracks = tracks;
906 metadata->no_tracks = i;
907 } else {
908 debug() << "no tracks available for playlist " << metadata->name
909 << endl;
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() );
915 uint32_t i = 0;
916 for( MtpMediaItem *it = dynamic_cast<MtpMediaItem *>(item->firstChild());
918 it = dynamic_cast<MtpMediaItem *>(it->nextSibling()) )
920 tracks[i] = it->track()->id();
921 i++;
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 );
932 if( ret == 0 )
934 item->playlist()->setId( metadata->playlist_id );
935 debug() << "playlist saved : " << metadata->playlist_id;
937 else
939 Amarok::StatusBar::instance()->shortLongMessage(
940 genericError,
941 i18n( "Could not create new playlist on device." ),
942 KDE::StatusBar::Error
946 else
948 metadata->playlist_id = item->playlist()->id();
949 debug() << "updating playlist : " << metadata->name;
950 int ret = LIBMTP_Update_Playlist( m_device, metadata );
951 if( ret != 0 )
953 Amarok::StatusBar::instance()->shortLongMessage(
954 genericError,
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 )
970 int result = 0;
971 if( isCanceled() || !item )
973 return -1;
975 MediaItem *next = 0;
977 switch( item->type() )
979 case MediaItem::PLAYLIST:
980 case MediaItem::TRACK:
981 if( isCanceled() )
982 break;
983 if( item )
985 int res = deleteObject( dynamic_cast<MtpMediaItem *> ( item ) );
986 if( res >=0 && result >= 0 )
987 result += res;
988 else
989 result = -1;
991 break;
992 case MediaItem::PLAYLISTITEM:
993 if( isCanceled() )
994 break;
995 if( item )
997 MtpMediaItem *parent = dynamic_cast<MtpMediaItem *> ( item->parent() );
998 if( parent && parent->type() == MediaItem::PLAYLIST ) {
999 delete( item );
1000 playlistFromItem( parent );
1003 break;
1004 case MediaItem::ALBUM:
1005 case MediaItem::ARTIST:
1006 // Recurse through the lists
1007 next = 0;
1009 if( isCanceled() )
1010 break;
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 )
1017 result += res;
1018 else
1019 result = -1;
1022 if( item )
1023 delete dynamic_cast<MediaItem *>( item );
1024 break;
1025 default:
1026 result = 0;
1028 return result;
1032 * Actually delete a track or playlist
1035 MtpMediaDevice::deleteObject( MtpMediaItem *deleteItem )
1037 DEBUG_BLOCK
1039 u_int32_t object_id;
1040 if( deleteItem->type() == MediaItem::PLAYLIST )
1041 object_id = deleteItem->playlist()->id();
1042 else
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();
1053 if( status != 0 )
1055 debug() << "delete object failed";
1056 Amarok::StatusBar::instance()->shortLongMessage(
1057 genericError,
1058 i18n( "Delete failed" ),
1059 KDE::StatusBar::Error
1061 return -1;
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
1069 delete deleteItem;
1070 kapp->processEvents( 100 );
1072 return 1;
1076 * Update local cache of mtp folders
1078 void
1079 MtpMediaDevice::updateFolders( void )
1081 LIBMTP_destroy_folder_t( m_folders );
1082 m_folders = 0;
1083 m_folders = LIBMTP_Get_Folder_List( m_device );
1087 * Set cancellation of an operation
1089 void
1090 MtpMediaDevice::cancelTransfer()
1092 m_canceled = true;
1096 * Connect to device, and populate m_view with MediaItems
1098 bool
1099 MtpMediaDevice::openDevice( bool silent )
1101 DEBUG_BLOCK
1103 Q_UNUSED( silent );
1105 if( m_device != 0 )
1106 return true;
1109 QString genericError = i18n( "Could not connect to MTP Device" );
1111 m_critical_mutex.lock();
1112 LIBMTP_Init();
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(
1118 genericError,
1119 i18n( "MTP device could not be opened" ),
1120 KDE::StatusBar::Error
1122 setDisconnected();
1123 return false;
1126 connect(
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 ) );
1133 m_name = modelname;
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 );
1147 if( ret == 0 )
1149 uint16_t i;
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" ) )
1155 m_format = "JPEG";
1156 else if( m_supportedFiles.findIndex( "png" ) )
1157 m_format = "PNG";
1158 else if( m_supportedFiles.findIndex( "gif" ) )
1159 m_format = "GIF";
1160 free( filetypes );
1161 m_critical_mutex.unlock();
1163 return true;
1167 * Start the view (add default folders such as for playlists)
1169 void
1170 MtpMediaDevice::initView()
1172 if( ! isConnected() )
1173 return;
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
1183 bool
1184 MtpMediaDevice::closeDevice()
1186 DEBUG_BLOCK
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();
1194 m_folders = 0;
1195 debug() << "Folders destroyed";
1198 // release device
1199 if( m_device != 0 )
1201 m_critical_mutex.lock();
1202 LIBMTP_Release_Device( m_device );
1203 m_critical_mutex.unlock();
1204 setDisconnected();
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
1214 clearItems();
1216 return true;
1220 * Get the capacity and freespace available on the device, in KB
1222 bool
1223 MtpMediaDevice::getCapacity( KIO::filesize_t *total, KIO::filesize_t *available )
1225 if( !isConnected() )
1226 return false;
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;
1231 return true;
1235 * Get custom information about the device via MTP
1237 void
1238 MtpMediaDevice::customClicked()
1240 QString Information;
1241 if( isConnected() )
1243 QString batteryLevel;
1244 QString secureTime;
1245 QString supportedFiles;
1247 uint8_t maxbattlevel;
1248 uint8_t currbattlevel;
1249 char *sectime;
1252 m_critical_mutex.lock();
1253 LIBMTP_Get_Batterylevel( m_device, &maxbattlevel, &currbattlevel );
1254 LIBMTP_Get_Secure_Time( m_device, &sectime );
1255 m_critical_mutex.unlock();
1257 batteryLevel = i18n("Battery level: ")
1258 + QString::number( (int) ( (float) currbattlevel / (float) maxbattlevel * 100.0 ) )
1259 + '%';
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'
1267 + supportedFiles );
1268 free(sectime);
1270 else
1272 Information = i18n( "Player not connected" );
1275 KMessageBox::information( 0, Information, i18n( "Device information" ) );
1279 * Current device
1281 LIBMTP_mtpdevice_t
1282 *MtpMediaDevice::current_device()
1284 return m_device;
1288 * We use a 0 device to show a disconnected device.
1289 * This sets the device to that.
1291 void
1292 MtpMediaDevice::setDisconnected()
1294 m_device = 0;
1298 * Handle clicking of the right mouse button
1300 void
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 );
1307 if( item )
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 );
1318 break;
1319 case MediaItem::PLAYLIST:
1320 menu.insertItem( SmallIconSet( Amarok::icon( "edit" ) ), i18n( "Rename" ), RENAME );
1321 break;
1322 default:
1323 break;
1326 menu.insertItem( SmallIconSet( Amarok::icon( "remove" ) ), i18n( "Delete from device" ), DELETE );
1328 int id = menu.exec( point );
1329 switch( id )
1331 case MAKE_PLAYLIST:
1333 Q3PtrList<MediaItem> items;
1334 m_view->getSelectedLeaves( 0, &items );
1335 QString name = i18n( "New Playlist" );
1336 newPlaylist( name, m_playlistItem, items );
1338 break;
1339 case DELETE:
1340 MediaDevice::deleteFromDevice();
1341 break;
1342 case RENAME:
1343 if( item->type() == MediaItem::PLAYLIST && ! isTransferring() )
1345 m_view->rename( item, 0 );
1347 break;
1348 case DOWNLOAD:
1349 downloadSelectedItemsToCollection();
1350 break;
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.",
1361 items->count()
1363 QString() );
1365 if( button != KMessageBox::Continue )
1366 return;
1368 updateAlbumArt( items );
1369 break;
1373 return;
1377 * Add gui elements to the device configuration
1379 void
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
1400 void
1401 MtpMediaDevice::removeConfigElements( QWidget *parent)
1403 Q_UNUSED(parent)
1405 delete m_folderStructureBox;
1406 m_folderStructureBox = 0;
1408 delete m_folderLabel;
1409 m_folderLabel = 0;
1413 * Save changed config after dialog commit
1415 void
1416 MtpMediaDevice::applyConfig()
1418 m_folderStructure = m_folderStructureBox->text();
1419 setConfigString( "FolderStructure", m_folderStructure );
1423 * Load config from the amarokrc file
1425 void
1426 MtpMediaDevice::loadConfig()
1428 m_folderStructure = configString( "FolderStructure","%a - %b" );
1432 * Add a track to the current list view
1434 MtpMediaItem
1435 *MtpMediaDevice::addTrackToView( MtpTrack *track, MtpMediaItem *item )
1437 QString artistName = track->bundle()->artist();
1439 MtpMediaItem *artist = dynamic_cast<MtpMediaItem *>( m_view->findItem( artistName, 0 ) );
1440 if( !artist )
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 ) );
1450 if( !album )
1452 album = new MtpMediaItem( artist );
1453 album->setText( 0, albumName );
1454 album->setType( MediaItem::ALBUM );
1455 album->m_device = this;
1458 if( item )
1459 album->insertItem( item );
1460 else
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;
1474 return item;
1478 * Get tracks and add them to the listview
1481 MtpMediaDevice::readMtpMusic()
1483 DEBUG_BLOCK
1485 clearItems();
1487 m_critical_mutex.lock();
1489 QString genericError = i18n( "Could not get music from MTP Device" );
1491 int total = 100;
1492 int progress = 0;
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";
1501 if( tracks == 0 )
1503 debug() << "0 tracks returned. Empty device...";
1505 else
1507 LIBMTP_track_t *tmp = tracks;
1508 total = 0;
1509 // spin through once to determine size of the list
1510 while( tracks != 0 )
1512 tracks = tracks->next;
1513 total++;
1515 setProgress( progress, total );
1516 tracks = tmp;
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 );
1523 tmp = tracks;
1524 tracks = tracks->next;
1525 LIBMTP_destroy_track_t( tmp );
1526 progress++;
1527 setProgress( progress );
1528 if( progress % 50 == 0 )
1529 kapp->processEvents( 100 );
1533 readPlaylists();
1534 readAlbums();
1536 setProgress( total );
1537 hideProgress();
1539 m_critical_mutex.unlock();
1541 return 0;
1545 * Populate playlists
1547 void
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 );
1562 uint32_t i;
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
1567 continue;
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 );
1573 item->m_order = i;
1574 item->m_device = this;
1576 tmp = playlists;
1577 playlists = playlists->next;
1578 LIBMTP_destroy_playlist_t( tmp );
1579 kapp->processEvents( 50 );
1586 * Read existing albums
1588 void
1589 MtpMediaDevice::readAlbums()
1591 LIBMTP_album_t *albums = LIBMTP_Get_Album_List( m_device );
1593 if( albums != 0 )
1595 LIBMTP_album_t *tmp;
1596 while( albums != 0 )
1598 m_idToAlbum[ albums->album_id ] = new MtpAlbum( albums );
1599 tmp = albums;
1600 albums = albums->next;
1601 LIBMTP_destroy_album_t( tmp );
1602 kapp->processEvents( 50 );
1608 * Clear the current listview
1610 void
1611 MtpMediaDevice::clearItems()
1613 m_view->clear();
1614 initView();
1618 * MtpTrack Class
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
1628 void
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 );
1651 else
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
1669 void
1670 MtpTrack::setBundle( MetaBundle &bundle )
1672 m_bundle = bundle;
1676 * MtpAlbum Class
1678 MtpAlbum::MtpAlbum( LIBMTP_album_t *album )
1680 m_id = album->album_id;
1681 m_album = QString::fromUtf8( album->name );