/*************************************************************************** * copyright : (C) 2006 Andy Kelk * ***************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * ***************************************************************************/ /** * Based on njb mediadevice with some code hints from the libmtp * example tools */ /** * MTP media device * @author Andy Kelk * @see http://libmtp.sourceforge.net/ */ #define DEBUG_PREFIX "MtpMediaDevice" #include #include #include "mtpmediadevice.h" AMAROK_EXPORT_PLUGIN( MtpMediaDevice ) // Amarok #include #include #include #include #include // KDE #include #include #include #include #include #include // TQt #include #include #include #include #include #include #include /** * MtpMediaDevice Class */ MtpMediaDevice::MtpMediaDevice() : MediaDevice() { m_name = i18n("MTP Media Device"); m_device = 0; m_folders = 0; m_playlistItem = 0; setDisconnected(); m_hasMountPoint = false; m_syncStats = false; m_transcode = false; m_transcodeAlways = false; m_transcodeRemove = false; m_configure = false; m_customButton = true; m_transfer = true; TDEToolBarButton *customButton = MediaBrowser::instance()->getToolBar()->getButton( MediaBrowser::CUSTOM ); customButton->setText( i18n("Special device functions") ); TQToolTip::remove( customButton ); TQToolTip::add( customButton, i18n( "Special functions of your device" ) ); mtpFileTypes[LIBMTP_FILETYPE_WAV] = "wav"; mtpFileTypes[LIBMTP_FILETYPE_MP3] = "mp3"; mtpFileTypes[LIBMTP_FILETYPE_WMA] = "wma"; mtpFileTypes[LIBMTP_FILETYPE_OGG] = "ogg"; mtpFileTypes[LIBMTP_FILETYPE_AUDIBLE] = "aa"; // audible mtpFileTypes[LIBMTP_FILETYPE_MP4] = "mp4"; mtpFileTypes[LIBMTP_FILETYPE_UNDEF_AUDIO] = "undef-audio"; mtpFileTypes[LIBMTP_FILETYPE_WMV] = "wmv"; mtpFileTypes[LIBMTP_FILETYPE_AVI] = "avi"; mtpFileTypes[LIBMTP_FILETYPE_MPEG] = "mpg"; mtpFileTypes[LIBMTP_FILETYPE_ASF] = "asf"; mtpFileTypes[LIBMTP_FILETYPE_QT] = "mov"; mtpFileTypes[LIBMTP_FILETYPE_UNDEF_VIDEO] = "undef-video"; mtpFileTypes[LIBMTP_FILETYPE_JPEG] = "jpg"; mtpFileTypes[LIBMTP_FILETYPE_JFIF] = "jpg"; mtpFileTypes[LIBMTP_FILETYPE_TIFF] = "tiff"; mtpFileTypes[LIBMTP_FILETYPE_BMP] = "bmp"; mtpFileTypes[LIBMTP_FILETYPE_GIF] = "gif"; mtpFileTypes[LIBMTP_FILETYPE_PICT] = "pict"; mtpFileTypes[LIBMTP_FILETYPE_PNG] = "png"; mtpFileTypes[LIBMTP_FILETYPE_VCALENDAR1] = "vcs"; // vcal1 mtpFileTypes[LIBMTP_FILETYPE_VCALENDAR2] = "vcs"; // vcal2 mtpFileTypes[LIBMTP_FILETYPE_VCARD2] = "vcf"; // vcard2 mtpFileTypes[LIBMTP_FILETYPE_VCARD3] = "vcf"; // vcard3 mtpFileTypes[LIBMTP_FILETYPE_WINDOWSIMAGEFORMAT] = "wim"; // windows image format mtpFileTypes[LIBMTP_FILETYPE_WINEXEC] = "exe"; mtpFileTypes[LIBMTP_FILETYPE_TEXT] = "txt"; mtpFileTypes[LIBMTP_FILETYPE_HTML] = "html"; mtpFileTypes[LIBMTP_FILETYPE_UNKNOWN] = "unknown"; m_newTracks = new TQPtrList; } void MtpMediaDevice::init( MediaBrowser *parent ) { MediaDevice::init( parent ); } bool MtpMediaDevice::isConnected() { return !( m_device == 0 ); } /** * File types that we support */ TQStringList MtpMediaDevice::supportedFiletypes() { return m_supportedFiles; } int MtpMediaDevice::progressCallback( uint64_t const sent, uint64_t const total, void const * const data ) { Q_UNUSED( sent ); Q_UNUSED( total ); kapp->processEvents( 100 ); MtpMediaDevice *dev = (MtpMediaDevice*)(data); if( dev->isCanceled() ) { debug() << "Canceling transfer operation" << endl; dev->setCanceled( true ); return 1; } return 0; } /** * Copy a track to the device */ MediaItem *MtpMediaDevice::copyTrackToDevice( const MetaBundle &bundle ) { DEBUG_BLOCK TQString genericError = i18n( "Could not send track" ); LIBMTP_track_t *trackmeta = LIBMTP_new_track_t(); trackmeta->item_id = 0; debug() << "filetype : " << bundle.fileType() << endl; if( bundle.fileType() == MetaBundle::mp3 ) { trackmeta->filetype = LIBMTP_FILETYPE_MP3; } else if( bundle.fileType() == MetaBundle::ogg ) { trackmeta->filetype = LIBMTP_FILETYPE_OGG; } else if( bundle.fileType() == MetaBundle::wma ) { trackmeta->filetype = LIBMTP_FILETYPE_WMA; } else if( bundle.fileType() == MetaBundle::mp4 ) { trackmeta->filetype = LIBMTP_FILETYPE_MP4; } else { // Couldn't recognise an Amarok filetype. // fallback to checking the extension (e.g. .wma, .ogg, etc) debug() << "No filetype found by Amarok filetype" << endl; const TQString extension = TQString(bundle.url().path().section( ".", -1 )).lower(); int libmtp_type = m_supportedFiles.findIndex( extension ); if( libmtp_type >= 0 ) { int keyIndex = mtpFileTypes.values().findIndex( extension ); libmtp_type = mtpFileTypes.keys()[keyIndex]; trackmeta->filetype = (LIBMTP_filetype_t) libmtp_type; debug() << "set filetype to " << libmtp_type << " based on extension of ." << extension << endl; } else { debug() << "We don't support the extension ." << extension << endl; Amarok::StatusBar::instance()->shortLongMessage( genericError, i18n( "Cannot determine a valid file type" ), KDE::StatusBar::Error ); return 0; } } if( bundle.title().isEmpty() ) { trackmeta->title = tqstrdup( i18n( "Unknown title" ).utf8() ); } else { trackmeta->title = tqstrdup( bundle.title().utf8() ); } if( bundle.album().isEmpty() ) { trackmeta->album = tqstrdup( i18n( "Unknown album" ).utf8() ); } else { trackmeta->album = tqstrdup( bundle.album().string().utf8() ); } if( bundle.artist().isEmpty() ) { trackmeta->artist = tqstrdup( i18n( "Unknown artist" ).utf8() ); } else { trackmeta->artist = tqstrdup( bundle.artist().string().utf8() ); } if( bundle.genre().isEmpty() ) { trackmeta->genre = tqstrdup( i18n( "Unknown genre" ).utf8() ); } else { trackmeta->genre = tqstrdup( bundle.genre().string().utf8() ); } if( bundle.year() > 0 ) { TQString date; TQTextOStream( &date ) << bundle.year() << "0101T0000.0"; trackmeta->date = tqstrdup( date.utf8() ); } else { trackmeta->date = tqstrdup( "00010101T0000.0" ); } if( bundle.track() > 0 ) { trackmeta->tracknumber = bundle.track(); } if( bundle.length() > 0 ) { // Multiply by 1000 since this is in milliseconds trackmeta->duration = bundle.length() * 1000; } if( !bundle.filename().isEmpty() ) { trackmeta->filename = tqstrdup( bundle.filename().utf8() ); } trackmeta->filesize = bundle.filesize(); // try and create the requested folder structure uint32_t parent_id = 0; if( !m_folderStructure.isEmpty() ) { parent_id = checkFolderStructure( bundle ); if( parent_id == 0 ) { debug() << "Couldn't create new parent (" << m_folderStructure << ")" << endl; Amarok::StatusBar::instance()->shortLongMessage( genericError, i18n( "Cannot create parent folder. Check your structure." ), KDE::StatusBar::Error ); return 0; } } else { parent_id = getDefaultParentId(); } debug() << "Parent id : " << parent_id << endl; trackmeta->parent_id = parent_id; m_critical_mutex.lock(); debug() << "Sending track... " << bundle.url().path().utf8() << endl; int ret = LIBMTP_Send_Track_From_File( m_device, bundle.url().path().utf8(), trackmeta, progressCallback, this ); m_critical_mutex.unlock(); if( ret < 0 ) { debug() << "Could not write file " << ret << endl; Amarok::StatusBar::instance()->shortLongMessage( genericError, i18n( "File write failed" ), KDE::StatusBar::Error ); return 0; } MetaBundle temp( bundle ); MtpTrack *taggedTrack = new MtpTrack( trackmeta ); taggedTrack->setBundle( temp ); taggedTrack->setFolderId( parent_id ); LIBMTP_destroy_track_t( trackmeta ); kapp->processEvents( 100 ); // add track to view and to new tracks list MediaItem *newItem = addTrackToView( taggedTrack ); m_newTracks->append( newItem ); return newItem; } /** * Get the cover image for a track, convert it to a format supported on the * device and set it as the cover art. */ void MtpMediaDevice::sendAlbumArt( TQPtrList *items ) { TQString image; image = CollectionDB::instance()->albumImage(items->first()->bundle()->artist(), items->first()->bundle()->album(), false, 100); if( ! image.endsWith( "@nocover.png" ) ) { debug() << "image " << image << " found for " << items->first()->bundle()->album() << endl; TQByteArray *imagedata = getSupportedImage( image ); if( imagedata == 0 ) { debug() << "Cannot generate a supported image format" << endl; return; } if( imagedata->size() ) { m_critical_mutex.lock(); LIBMTP_album_t *album_object = getOrCreateAlbum( items ); if( album_object ) { LIBMTP_filesampledata_t *imagefile = LIBMTP_new_filesampledata_t(); imagefile->data = (char *) imagedata->data(); imagefile->size = imagedata->size(); imagefile->filetype = LIBMTP_FILETYPE_JPEG; int ret = LIBMTP_Send_Representative_Sample( m_device, album_object->album_id, imagefile ); if( ret != 0 ) { debug() << "image send failed : " << ret << endl; } } m_critical_mutex.unlock(); } } } uint32_t MtpMediaDevice::getDefaultParentId( void ) { // Decide which folder to send it to: // If the device gave us a parent_folder setting, we use it uint32_t parent_id = 0; if( m_default_parent_folder ) { parent_id = m_default_parent_folder; } // Otherwise look for a folder called "Music" else if( m_folders != 0 ) { parent_id = folderNameToID( "Music", m_folders ); if( !parent_id ) { debug() << "Parent folder could not be found. Going to use top level." << endl; } } // Give up and don't set a parent folder, let the device deal with it else { debug() << "No folders found. Going to use top level." << endl; } return parent_id; } /** * Takes path to an existing cover image and converts it to a format * supported on the device */ TQByteArray *MtpMediaDevice::getSupportedImage( TQString path ) { if( m_format == 0 ) return 0; debug() << "Will convert image to " << m_format << endl; // open image const TQImage original( path ); // save as new image TQImage newformat( original ); TQByteArray *newimage = new TQByteArray(); TQBuffer buffer( *newimage ); buffer.open( IO_WriteOnly ); if( newformat.save( &buffer, m_format.ascii() ) ) { buffer.close(); return newimage; } return 0; } /** * Update cover art for a number of tracks */ void MtpMediaDevice::updateAlbumArt( TQPtrList *items ) { DEBUG_BLOCK if( m_format == 0 ) // no supported image types. Don't even bother. return; setCanceled( false ); kapp->processEvents( 100 ); TQMap< TQString, TQPtrList > albumList; for( MtpMediaItem *it = dynamic_cast(items->first()); it && !(m_canceled); it = dynamic_cast(items->next()) ) { // build album list if( it->type() == MediaItem::TRACK ) { albumList[ it->bundle()->album() ].append( it ); } if( it->type() == MediaItem::ALBUM ) { debug() << "look, we get albums too!" << endl; } } int i = 0; setProgress( i, albumList.count() ); kapp->processEvents( 100 ); TQMap< TQString, TQPtrList >::Iterator it; for( it = albumList.begin(); it != albumList.end(); ++it ) { sendAlbumArt( &it.data() ); setProgress( ++i ); if( i % 20 == 0 ) kapp->processEvents( 100 ); } hideProgress(); } /** * Retrieve existing or create new album object. */ LIBMTP_album_t *MtpMediaDevice::getOrCreateAlbum( TQPtrList *items )//uint32_t track_id, const MetaBundle &bundle ) { LIBMTP_album_t *album_object = 0; uint32_t albumid = 0; int ret; TQMap::Iterator it; for( it = m_idToAlbum.begin(); it != m_idToAlbum.end(); ++it ) { if( it.data()->album() == items->first()->bundle()->album() ) { albumid = it.data()->id(); break; } } if( albumid ) { debug() << "reusing existing album " << albumid << endl; album_object = LIBMTP_Get_Album( m_device, albumid ); if( album_object == 0 ) { debug() << "retrieving album failed." << endl; return 0; } uint32_t i; uint32_t trackCount = album_object->no_tracks; for( MtpMediaItem *it = dynamic_cast(items->first()); it; it = dynamic_cast(items->next()) ) { bool exists = false; for( i = 0; i < album_object->no_tracks; i++ ) { if( album_object->tracks[i] == it->track()->id() ) { exists = true; break; } } if( ! exists ) { debug() << "adding track " << it->track()->id() << " to existing album " << albumid << endl; album_object->no_tracks++; album_object->tracks = (uint32_t *)realloc( album_object->tracks, album_object->no_tracks * sizeof( uint32_t ) ); album_object->tracks[ ( album_object->no_tracks - 1 ) ] = it->track()->id(); } } if( trackCount != album_object->no_tracks ) // album needs an update { ret = LIBMTP_Update_Album( m_device, album_object ); if( ret != 0 ) debug() << "updating album failed : " << ret << endl; } } else { debug() << "creating new album " << endl; album_object = LIBMTP_new_album_t(); album_object->name = tqstrdup( items->first()->bundle()->album().string().utf8() ); album_object->tracks = (uint32_t *) malloc(items->count() * sizeof(uint32_t)); int i = 0; for( MtpMediaItem *it = dynamic_cast(items->first()); it; it = dynamic_cast(items->next()) ) album_object->tracks[i++] = it->track()->id(); album_object->no_tracks = items->count(); ret = LIBMTP_Create_New_Album( m_device, album_object ); if( ret != 0 ) { debug() << "creating album failed : " << ret << endl; return 0; } m_idToAlbum[ album_object->album_id ] = new MtpAlbum( album_object ); } return album_object; } /** * Check (and optionally create) the folder structure to put a * track into. Return the (possibly new) parent folder ID */ uint32_t MtpMediaDevice::checkFolderStructure( const MetaBundle &bundle, bool create ) { TQString artist = bundle.artist(); if( artist.isEmpty() ) artist = i18n( "Unknown Artist" ); if( bundle.compilation() == MetaBundle::CompilationYes ) artist = i18n( "Various Artists" ); TQString album = bundle.album(); if( album.isEmpty() ) album = i18n( "Unknown Album" ); TQString genre = bundle.genre(); if( genre.isEmpty() ) genre = i18n( "Unknown Genre" ); m_critical_mutex.lock(); uint32_t parent_id = getDefaultParentId(); TQStringList folders = TQStringList::split( "/", m_folderStructure ); // use slash as a dir separator TQString completePath; for( TQStringList::Iterator it = folders.begin(); it != folders.end(); ++it ) { if( (*it).isEmpty() ) continue; // substitute %a , %b , %g (*it).replace( TQRegExp( "%a" ), artist ) .replace( TQRegExp( "%b" ), album ) .replace( TQRegExp( "%g" ), genre ); // check if it exists uint32_t check_folder = subfolderNameToID( (*it).utf8(), m_folders, parent_id ); // create if not exists (if requested) if( check_folder == 0 ) { if( create ) { check_folder = createFolder( (*it).utf8() , parent_id ); if( check_folder == 0 ) { m_critical_mutex.unlock(); return 0; } } else { m_critical_mutex.unlock(); return 0; } } completePath += (*it).utf8() + '/'; // set new parent parent_id = check_folder; } m_critical_mutex.unlock(); debug() << "Folder path : " << completePath << endl; // return parent return parent_id; } /** * Create a new mtp folder */ uint32_t MtpMediaDevice::createFolder( const char *name, uint32_t parent_id ) { debug() << "Creating new folder '" << name << "' as a child of "<< parent_id << endl; char *name_copy = tqstrdup( name ); uint32_t new_folder_id = LIBMTP_Create_Folder( m_device, name_copy, parent_id, 0 ); delete(name_copy); debug() << "New folder ID: " << new_folder_id << endl; if( new_folder_id == 0 ) { debug() << "Attempt to create folder '" << name << "' failed." << endl; return 0; } updateFolders(); return new_folder_id; } /** * Recursively search the folder list for a matching one under the specified * parent ID and return the child's ID */ uint32_t MtpMediaDevice::subfolderNameToID( const char *name, LIBMTP_folder_t *folderlist, uint32_t parent_id ) { uint32_t i; if( folderlist == 0 ) return 0; if( !strcasecmp( name, folderlist->name ) && folderlist->parent_id == parent_id ) return folderlist->folder_id; if( ( i = ( subfolderNameToID( name, folderlist->child, parent_id ) ) ) ) return i; if( ( i = ( subfolderNameToID( name, folderlist->sibling, parent_id ) ) ) ) return i; return 0; } /** * Recursively search the folder list for a matching one * and return its ID */ uint32_t MtpMediaDevice::folderNameToID( char *name, LIBMTP_folder_t *folderlist ) { uint32_t i; if( folderlist == 0 ) return 0; if( !strcasecmp( name, folderlist->name ) ) return folderlist->folder_id; if( ( i = ( folderNameToID( name, folderlist->child ) ) ) ) return i; if( ( i = ( folderNameToID( name, folderlist->sibling ) ) ) ) return i; return 0; } /** * Get a list of selected items, download them to a temporary location and * organize. */ int MtpMediaDevice::downloadSelectedItemsToCollection() { TQPtrList items; m_view->getSelectedLeaves( 0, &items ); KTempDir tempdir; tempdir.setAutoDelete( true ); KURL::List urls; TQString genericError = i18n( "Could not copy track from device." ); int total,progress; total = items.count(); progress = 0; if( total == 0 ) return 0; setProgress( progress, total ); for( MtpMediaItem *it = dynamic_cast(items.first()); it && !(m_canceled); it = dynamic_cast(items.next()) ) { if( it->type() == MediaItem::TRACK ) { TQString filename = tempdir.name() + it->bundle()->filename(); int ret = LIBMTP_Get_Track_To_File( m_device, it->track()->id(), filename.utf8(), progressCallback, this ); if( ret != 0 ) { debug() << "Get Track failed: " << ret << endl; Amarok::StatusBar::instance()->shortLongMessage( genericError, i18n( "Could not copy track from device." ), KDE::StatusBar::Error ); } else { urls << filename; progress++; setProgress( progress ); } } else { total--; setProgress( progress, total ); } } hideProgress(); CollectionView::instance()->organizeFiles( urls, i18n( "Move Files To Collection" ), false ); return 0; } /** * Write any pending changes to the device, such as database changes */ void MtpMediaDevice::synchronizeDevice() { updateAlbumArt( m_newTracks ); m_newTracks->clear(); return; } /** * Find an existing track */ MediaItem *MtpMediaDevice::trackExists( const MetaBundle &bundle ) { MediaItem *artist = dynamic_cast( m_view->findItem( bundle.artist(), 0 ) ); if( artist ) { MediaItem *album = dynamic_cast( artist->findItem( bundle.album() ) ); if( album ) { MediaItem *track = dynamic_cast( album->findItem( bundle.title() ) ); if( track ) return track; } } uint32_t folderId = checkFolderStructure( bundle, false ); MediaItem *file = m_fileNameToItem[ TQString( "%1/%2" ).arg( folderId ).arg( bundle.filename() ) ]; if( file != 0 ) return file; return 0; } /** * Create a new playlist */ MtpMediaItem *MtpMediaDevice::newPlaylist( const TQString &name, MediaItem *parent, TQPtrList items ) { DEBUG_BLOCK MtpMediaItem *item = new MtpMediaItem( parent, this ); item->setType( MediaItem::PLAYLIST ); item->setText( 0, name ); item->setPlaylist( new MtpPlaylist() ); addToPlaylist( item, 0, items ); if( ! isTransferring() ) m_view->rename( item, 0 ); return item; } /** * Add an item to a playlist */ void MtpMediaDevice::addToPlaylist( MediaItem *mlist, MediaItem *after, TQPtrList items ) { DEBUG_BLOCK MtpMediaItem *list = dynamic_cast( mlist ); if( !list ) return; int order; MtpMediaItem *it; if( after ) { order = after->m_order + 1; it = dynamic_cast(after->nextSibling()); } else { order = 0; it = dynamic_cast( list->firstChild() ); } for( ; it; it = dynamic_cast( it->nextSibling() ) ) { it->m_order += items.count(); } for( MtpMediaItem *it = dynamic_cast(items.first() ); it; it = dynamic_cast( items.next() ) ) { if( !it->track() ) continue; MtpMediaItem *add; if( it->parent() == list ) { add = it; if( after ) { it->moveItem(after); } else { list->takeItem(it); list->insertItem(it); } } else { if( after ) { add = new MtpMediaItem( list, after ); } else { add = new MtpMediaItem( list, this ); } } after = add; add->setType( MediaItem::PLAYLISTITEM ); add->setTrack( it->track() ); add->setBundle( new MetaBundle( *(it->bundle()) ) ); add->m_device = this; add->setText( 0, it->bundle()->artist() + " - " + it->bundle()->title() ); add->m_order = order; order++; } // make numbering consecutive int i = 0; for( MtpMediaItem *it = dynamic_cast( list->firstChild() ); it; it = dynamic_cast( it->nextSibling() ) ) { it->m_order = i; i++; } playlistFromItem( list ); } /** * When a playlist has been renamed, we must save it */ void MtpMediaDevice::playlistRenamed( TQListViewItem *item, const TQString &, int ) // SLOT { DEBUG_BLOCK MtpMediaItem *playlist = static_cast( item ); if( playlist->type() == MediaItem::PLAYLIST ) playlistFromItem( playlist ); } /** * Save a playlist */ void MtpMediaDevice::playlistFromItem( MtpMediaItem *item ) { if( item->childCount() == 0 ) return; m_critical_mutex.lock(); LIBMTP_playlist_t *metadata = LIBMTP_new_playlist_t(); metadata->name = tqstrdup( item->text( 0 ).utf8() ); const int trackCount = item->childCount(); if (trackCount > 0) { uint32_t *tracks = ( uint32_t* )malloc( sizeof( uint32_t ) * trackCount ); uint32_t i = 0; for( MtpMediaItem *it = dynamic_cast(item->firstChild()); it; it = dynamic_cast(it->nextSibling()) ) { tracks[i] = it->track()->id(); i++; } metadata->tracks = tracks; metadata->no_tracks = i; } else { debug() << "no tracks available for playlist " << metadata->name << endl; metadata->no_tracks = 0; } TQString genericError = i18n( "Could not save playlist." ); if( item->playlist()->id() == 0 ) { debug() << "creating new playlist : " << metadata->name << endl; int ret = LIBMTP_Create_New_Playlist( m_device, metadata ); if( ret == 0 ) { item->playlist()->setId( metadata->playlist_id ); debug() << "playlist saved : " << metadata->playlist_id << endl; } else { Amarok::StatusBar::instance()->shortLongMessage( genericError, i18n( "Could not create new playlist on device." ), KDE::StatusBar::Error ); } } else { metadata->playlist_id = item->playlist()->id(); debug() << "updating playlist : " << metadata->name << endl; int ret = LIBMTP_Update_Playlist( m_device, metadata ); if( ret != 0 ) { Amarok::StatusBar::instance()->shortLongMessage( genericError, i18n( "Could not update playlist on device." ), KDE::StatusBar::Error ); } } m_critical_mutex.unlock(); } /** * Recursively remove MediaItem from the device and media view */ int MtpMediaDevice::deleteItemFromDevice(MediaItem* item, int flags ) { int result = 0; if( isCanceled() || !item ) { return -1; } MediaItem *next = 0; switch( item->type() ) { case MediaItem::PLAYLIST: case MediaItem::TRACK: if( isCanceled() ) break; if( item ) { int res = deleteObject( dynamic_cast ( item ) ); if( res >=0 && result >= 0 ) result += res; else result = -1; } break; case MediaItem::PLAYLISTITEM: if( isCanceled() ) break; if( item ) { MtpMediaItem *parent = dynamic_cast ( item->parent() ); if( parent && parent->type() == MediaItem::PLAYLIST ) { delete( item ); playlistFromItem( parent ); } } break; case MediaItem::ALBUM: case MediaItem::ARTIST: // Recurse through the lists next = 0; if( isCanceled() ) break; for( MediaItem *it = dynamic_cast( item->firstChild() ); it ; it = next ) { next = dynamic_cast( it->nextSibling() ); int res = deleteItemFromDevice( it, flags ); if( res >= 0 && result >= 0 ) result += res; else result = -1; } if( item ) delete dynamic_cast( item ); break; default: result = 0; } return result; } /** * Actually delete a track or playlist */ int MtpMediaDevice::deleteObject( MtpMediaItem *deleteItem ) { DEBUG_BLOCK u_int32_t object_id; if( deleteItem->type() == MediaItem::PLAYLIST ) object_id = deleteItem->playlist()->id(); else object_id = deleteItem->track()->id(); TQString genericError = i18n( "Could not delete item" ); debug() << "delete this id : " << object_id << endl; m_critical_mutex.lock(); int status = LIBMTP_Delete_Object( m_device, object_id ); m_critical_mutex.unlock(); if( status != 0 ) { debug() << "delete object failed" << endl; Amarok::StatusBar::instance()->shortLongMessage( genericError, i18n( "Delete failed" ), KDE::StatusBar::Error ); return -1; } debug() << "object deleted" << endl; // clear cached filename if( deleteItem->type() == MediaItem::TRACK ) m_fileNameToItem.remove( TQString( "%1/%2" ).arg( deleteItem->track()->folderId() ).arg( deleteItem->bundle()->filename() ) ); // remove from the media view delete deleteItem; kapp->processEvents( 100 ); return 1; } /** * Update local cache of mtp folders */ void MtpMediaDevice::updateFolders( void ) { LIBMTP_destroy_folder_t( m_folders ); m_folders = 0; m_folders = LIBMTP_Get_Folder_List( m_device ); } /** * Set cancellation of an operation */ void MtpMediaDevice::cancelTransfer() { m_canceled = true; } /** * Connect to device, and populate m_view with MediaItems */ bool MtpMediaDevice::openDevice( bool silent ) { DEBUG_BLOCK Q_UNUSED( silent ); if( m_device != 0 ) return true; TQString genericError = i18n( "Could not connect to MTP Device" ); m_critical_mutex.lock(); LIBMTP_Init(); m_device = LIBMTP_Get_First_Device(); m_critical_mutex.unlock(); if( m_device == 0 ) { debug() << "No devices." << endl; Amarok::StatusBar::instance()->shortLongMessage( genericError, i18n( "MTP device could not be opened" ), KDE::StatusBar::Error ); setDisconnected(); return false; } connect( m_view, TQT_SIGNAL( itemRenamed( TQListViewItem*, const TQString&, int ) ), this, TQT_SLOT( playlistRenamed( TQListViewItem*, const TQString&, int ) ) ); TQString modelname = TQString( LIBMTP_Get_Modelname( m_device ) ); TQString ownername = TQString( LIBMTP_Get_Friendlyname( m_device ) ); m_name = modelname; if(! ownername.isEmpty() ) m_name += " (" + ownername + ')'; m_default_parent_folder = m_device->default_music_folder; debug() << "setting default parent : " << m_default_parent_folder << endl; MtpMediaDevice::readMtpMusic(); m_critical_mutex.lock(); m_folders = LIBMTP_Get_Folder_List( m_device ); uint16_t *filetypes; uint16_t filetypes_len; int ret = LIBMTP_Get_Supported_Filetypes( m_device, &filetypes, &filetypes_len ); if( ret == 0 ) { uint16_t i; for( i = 0; i < filetypes_len; i++ ) m_supportedFiles << mtpFileTypes[ filetypes[ i ] ]; } // find supported image types (for album art). if( m_supportedFiles.findIndex( "jpg" ) ) m_format = "JPEG"; else if( m_supportedFiles.findIndex( "png" ) ) m_format = "PNG"; else if( m_supportedFiles.findIndex( "gif" ) ) m_format = "GIF"; free( filetypes ); m_critical_mutex.unlock(); return true; } /** * Start the view (add default folders such as for playlists) */ void MtpMediaDevice::initView() { if( ! isConnected() ) return; m_playlistItem = new MtpMediaItem( m_view, this ); m_playlistItem->setText( 0, i18n("Playlists") ); m_playlistItem->setType( MediaItem::PLAYLISTSROOT ); m_playlistItem->m_order = -1; } /** * Wrap up any loose ends and close the device */ bool MtpMediaDevice::closeDevice() { DEBUG_BLOCK // clear folder structure if( m_folders != 0 ) { m_critical_mutex.lock(); LIBMTP_destroy_folder_t( m_folders ); m_critical_mutex.unlock(); m_folders = 0; debug() << "Folders destroyed" << endl; } // release device if( m_device != 0 ) { m_critical_mutex.lock(); LIBMTP_Release_Device( m_device ); m_critical_mutex.unlock(); setDisconnected(); debug() << "Device released" << endl; } // clear the cached mappings m_idToAlbum.clear(); m_idToTrack.clear(); m_fileNameToItem.clear(); // clean up the view clearItems(); return true; } /** * Get the capacity and freespace available on the device, in KB */ bool MtpMediaDevice::getCapacity( TDEIO::filesize_t *total, TDEIO::filesize_t *available ) { if( !isConnected() ) return false; // TODO : Follow the links so we sum up all the device's storage. *total = m_device->storage->MaxCapacity; *available = m_device->storage->FreeSpaceInBytes; return true; } /** * Get custom information about the device via MTP */ void MtpMediaDevice::customClicked() { TQString Information; if( isConnected() ) { TQString batteryLevel; TQString secureTime; TQString supportedFiles; uint8_t maxbattlevel; uint8_t currbattlevel; char *sectime; m_critical_mutex.lock(); LIBMTP_Get_Batterylevel( m_device, &maxbattlevel, &currbattlevel ); LIBMTP_Get_Secure_Time( m_device, §ime ); m_critical_mutex.unlock(); batteryLevel = i18n("Battery level: ") + TQString::number( (int) ( (float) currbattlevel / (float) maxbattlevel * 100.0 ) ) + '%'; secureTime = i18n("Secure time: ") + sectime; supportedFiles = i18n("Supported file types: ") + m_supportedFiles.join( ", " ); Information = ( i18n( "Player Information for " ) + m_name + '\n' + batteryLevel + '\n' + secureTime + '\n' + supportedFiles ); free(sectime); } else { Information = i18n( "Player not connected" ); } KMessageBox::information( 0, Information, i18n( "Device information" ) ); } /** * Current device */ LIBMTP_mtpdevice_t *MtpMediaDevice::current_device() { return m_device; } /** * We use a 0 device to show a disconnected device. * This sets the device to that. */ void MtpMediaDevice::setDisconnected() { m_device = 0; } /** * Handle clicking of the right mouse button */ void MtpMediaDevice::rmbPressed( TQListViewItem *qitem, const TQPoint &point, int ) { enum Actions {RENAME, DOWNLOAD, DELETE, MAKE_PLAYLIST, UPDATE_ALBUM_ART}; MtpMediaItem *item = static_cast( qitem ); if( item ) { TDEPopupMenu menu( m_view ); switch( item->type() ) { case MediaItem::ARTIST: case MediaItem::ALBUM: case MediaItem::TRACK: menu.insertItem( SmallIconSet( Amarok::icon( "collection" ) ), i18n("&Copy Files to Collection..."), DOWNLOAD ); menu.insertItem( SmallIconSet( Amarok::icon( "playlist" ) ), i18n( "Make Media Device Playlist" ), MAKE_PLAYLIST ); menu.insertItem( SmallIconSet( Amarok::icon( "covermanager" ) ), i18n( "Refresh Cover Images" ), UPDATE_ALBUM_ART ); break; case MediaItem::PLAYLIST: menu.insertItem( SmallIconSet( Amarok::icon( "edit" ) ), i18n( "Rename" ), RENAME ); break; default: break; } menu.insertItem( SmallIconSet( Amarok::icon( "remove" ) ), i18n( "Delete from device" ), DELETE ); int id = menu.exec( point ); switch( id ) { case MAKE_PLAYLIST: { TQPtrList items; m_view->getSelectedLeaves( 0, &items ); TQString name = i18n( "New Playlist" ); newPlaylist( name, m_playlistItem, items ); } break; case DELETE: MediaDevice::deleteFromDevice(); break; case RENAME: if( item->type() == MediaItem::PLAYLIST && ! isTransferring() ) { m_view->rename( item, 0 ); } break; case DOWNLOAD: downloadSelectedItemsToCollection(); break; case UPDATE_ALBUM_ART: { TQPtrList *items = new TQPtrList; m_view->getSelectedLeaves( 0, items ); if( items->count() > 100 ) { int button = KMessageBox::warningContinueCancel( m_parent, i18n( "

You are updating cover art for 1 track. This may take some time.", "

You are updating cover art for %n tracks. This may take some time.", items->count() ), TQString() ); if( button != KMessageBox::Continue ) return; } updateAlbumArt( items ); break; } } } return; } /** * Add gui elements to the device configuration */ void MtpMediaDevice::addConfigElements( TQWidget *parent ) { m_folderLabel = new TQLabel( parent ); m_folderLabel->setText( i18n( "Folder structure:" ) ); m_folderStructureBox = new TQLineEdit( parent ); m_folderStructureBox->setText( m_folderStructure ); TQToolTip::add( m_folderStructureBox, i18n( "Files copied to the device will be placed in this folder." ) + '\n' + i18n( "/ is used as folder separator." ) + '\n' + i18n( "%a will be replaced with the artist name, ") + i18n( "%b with the album name," ) + '\n' + i18n( "%g with the genre.") + '\n' + i18n( "An empty path means the files will be placed unsorted in the default music folder." ) ); } /** * Remove gui elements from the device configuration */ void MtpMediaDevice::removeConfigElements( TQWidget *parent) { Q_UNUSED(parent) delete m_folderStructureBox; m_folderStructureBox = 0; delete m_folderLabel; m_folderLabel = 0; } /** * Save changed config after dialog commit */ void MtpMediaDevice::applyConfig() { m_folderStructure = m_folderStructureBox->text(); setConfigString( "FolderStructure", m_folderStructure ); } /** * Load config from the amarokrc file */ void MtpMediaDevice::loadConfig() { m_folderStructure = configString( "FolderStructure","%a - %b" ); } /** * Add a track to the current list view */ MtpMediaItem *MtpMediaDevice::addTrackToView( MtpTrack *track, MtpMediaItem *item ) { TQString artistName = track->bundle()->artist(); MtpMediaItem *artist = dynamic_cast( m_view->findItem( artistName, 0 ) ); if( !artist ) { artist = new MtpMediaItem(m_view); artist->m_device = this; artist->setText( 0, artistName ); artist->setType( MediaItem::ARTIST ); } TQString albumName = track->bundle()->album(); MtpMediaItem *album = dynamic_cast( artist->findItem( albumName ) ); if( !album ) { album = new MtpMediaItem( artist ); album->setText( 0, albumName ); album->setType( MediaItem::ALBUM ); album->m_device = this; } if( item ) album->insertItem( item ); else { item = new MtpMediaItem( album ); item->m_device = this; TQString titleName = track->bundle()->title(); item->setTrack( track ); item->m_order = track->bundle()->track(); item->setText( 0, titleName ); item->setType( MediaItem::TRACK ); item->setBundle( track->bundle() ); item->track()->setId( track->id() ); m_fileNameToItem[ TQString( "%1/%2" ).arg( track->folderId() ).arg( track->bundle()->filename() ) ] = item; m_idToTrack[ track->id() ] = track; } return item; } /** * Get tracks and add them to the listview */ int MtpMediaDevice::readMtpMusic() { DEBUG_BLOCK clearItems(); m_critical_mutex.lock(); TQString genericError = i18n( "Could not get music from MTP Device" ); int total = 100; int progress = 0; setProgress( progress, total ); // we don't know how many tracks. fake progress bar. kapp->processEvents( 100 ); LIBMTP_track_t *tracks = LIBMTP_Get_Tracklisting_With_Callback( m_device, progressCallback, this ); debug() << "Got tracks from device" << endl; if( tracks == 0 ) { debug() << "0 tracks returned. Empty device..." << endl; } else { LIBMTP_track_t *tmp = tracks; total = 0; // spin through once to determine size of the list while( tracks != 0 ) { tracks = tracks->next; total++; } setProgress( progress, total ); tracks = tmp; // now process the tracks while( tracks != 0 ) { MtpTrack *mtp_track = new MtpTrack( tracks ); mtp_track->readMetaData( tracks ); addTrackToView( mtp_track ); tmp = tracks; tracks = tracks->next; LIBMTP_destroy_track_t( tmp ); progress++; setProgress( progress ); if( progress % 50 == 0 ) kapp->processEvents( 100 ); } } readPlaylists(); readAlbums(); setProgress( total ); hideProgress(); m_critical_mutex.unlock(); return 0; } /** * Populate playlists */ void MtpMediaDevice::readPlaylists() { LIBMTP_playlist_t *playlists = LIBMTP_Get_Playlist_List( m_device ); if( playlists != 0 ) { LIBMTP_playlist_t *tmp; while( playlists != 0 ) { MtpMediaItem *playlist = new MtpMediaItem( m_playlistItem, this ); playlist->setText( 0, TQString::fromUtf8( playlists->name ) ); playlist->setType( MediaItem::PLAYLIST ); playlist->setPlaylist( new MtpPlaylist() ); playlist->playlist()->setId( playlists->playlist_id ); uint32_t i; for( i = 0; i < playlists->no_tracks; i++ ) { MtpTrack *track = m_idToTrack[ playlists->tracks[i] ]; if( track == 0 ) // skip invalid playlist entries continue; MtpMediaItem *item = new MtpMediaItem( playlist ); item->setText( 0, track->bundle()->artist() + " - " + track->bundle()->title() ); item->setType( MediaItem::PLAYLISTITEM ); item->setBundle( track->bundle() ); item->setTrack( track ); item->m_order = i; item->m_device = this; } tmp = playlists; playlists = playlists->next; LIBMTP_destroy_playlist_t( tmp ); kapp->processEvents( 50 ); } } } /** * Read existing albums */ void MtpMediaDevice::readAlbums() { LIBMTP_album_t *albums = LIBMTP_Get_Album_List( m_device ); if( albums != 0 ) { LIBMTP_album_t *tmp; while( albums != 0 ) { m_idToAlbum[ albums->album_id ] = new MtpAlbum( albums ); tmp = albums; albums = albums->next; LIBMTP_destroy_album_t( tmp ); kapp->processEvents( 50 ); } } } /** * Clear the current listview */ void MtpMediaDevice::clearItems() { m_view->clear(); initView(); } /** * MtpTrack Class */ MtpTrack::MtpTrack( LIBMTP_track_t *track ) { m_id = track->item_id; m_folder_id = 0; } /** * Read track properties from the device and set it on the track */ void MtpTrack::readMetaData( LIBMTP_track_t *track ) { MetaBundle *bundle = new MetaBundle(); if( track->genre != 0 ) bundle->setGenre( AtomicString( TQString::fromUtf8( track->genre ) ) ); if( track->artist != 0 ) bundle->setArtist( AtomicString( TQString::fromUtf8( track->artist ) ) ); if( track->album != 0 ) bundle->setAlbum( AtomicString( TQString::fromUtf8( track->album ) ) ); if( track->title != 0 ) bundle->setTitle( AtomicString( TQString::fromUtf8( track->title ) ) ); if( track->filename != 0 ) bundle->setPath( AtomicString( TQString::fromUtf8( track->filename ) ) ); // translate codecs to file types if( track->filetype == LIBMTP_FILETYPE_MP3 ) bundle->setFileType( MetaBundle::mp3 ); else if( track->filetype == LIBMTP_FILETYPE_WMA ) bundle->setFileType( MetaBundle::wma ); else if( track->filetype == LIBMTP_FILETYPE_OGG ) bundle->setFileType( MetaBundle::ogg ); else bundle->setFileType( MetaBundle::other ); if( track->date != 0 ) bundle->setYear( TQString( TQString::fromUtf8( track->date ) ).mid( 0, 4 ).toUInt() ); if( track->tracknumber > 0 ) bundle->setTrack( track->tracknumber ); if( track->duration > 0 ) bundle->setLength( track->duration / 1000 ); // Divide by 1000 since this is in milliseconds this->setFolderId( track->parent_id ); this->setBundle( *bundle ); } /** * Set this track's metabundle */ void MtpTrack::setBundle( MetaBundle &bundle ) { m_bundle = bundle; } /** * MtpAlbum Class */ MtpAlbum::MtpAlbum( LIBMTP_album_t *album ) { m_id = album->album_id; m_album = TQString::fromUtf8( album->name ); } #include "mtpmediadevice.moc"