Amarok – versatile and easy to use audio player
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

2706 lines
83 KiB

/***************************************************************************
copyright : (C) 2005, 2006 by Martin Aumueller
email : aumuell@reserv.at
copyright : (C) 2004 by Christian Muehlhaeuser
email : chris@chris.de
***************************************************************************/
/***************************************************************************
* This library is free software; you can redistribute it and/or modify *
* it under the terms of the GNU General Public License version 2 as *
* published by the Free Software Foundation. *
* *
* This library is distributed in the hope that it will be useful, but *
* WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU *
* Lesser General Public License for more details. *
* *
* You should have received a copy of the GNU General Public License *
* along with this library; if not, write to the Free Software *
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, *
* MA 02110-1301 USA *
***************************************************************************/
#define DEBUG_PREFIX "IpodMediaDevice"
#include <config.h>
#include "ipodmediadevice.h"
AMAROK_EXPORT_PLUGIN( IpodMediaDevice )
#include <debug.h>
#include <metabundle.h>
#include <collectiondb.h>
#include <statusbar/statusbar.h>
#include <k3bexporter.h>
#include <playlist.h>
#include <collectionbrowser.h>
#include <playlistbrowser.h>
#include <tagdialog.h>
#include <threadmanager.h>
#include <metadata/tplugins.h>
#include <hintlineedit.h>
#include <tdeactionclasses.h>
#include <tdeapplication.h>
#include <kmountpoint.h>
#include <kpushbutton.h>
#include <kprogress.h>
#include <tdemessagebox.h>
#include <kiconloader.h>
#include <tdepopupmenu.h>
#include <tqcheckbox.h>
#include <tqdir.h>
#include <tqfileinfo.h>
#include <tqlabel.h>
#include <tqlineedit.h>
#include <tqregexp.h>
#include <tqtimer.h>
#include <tqtooltip.h>
#ifdef HAVE_STATVFS
#include <stdint.h>
#include <sys/statvfs.h>
#endif
#include <cstdlib>
#include <unistd.h>
#ifndef HAVE_ITDB_MEDIATYPE
#define mediatype unk208
#endif
#include "metadata/audible/taglib_audiblefile.h"
struct PodcastInfo
{
// per show
TQString url;
TQString description;
TQDateTime date;
TQString author;
bool listened;
// per channel
TQString rss;
PodcastInfo() { listened = false; }
};
class TrackList : public TQPtrList<Itdb_Track>
{
int compareItems ( TQPtrCollection::Item track1, TQPtrCollection::Item track2 )
{
Itdb_Track *t1 = (Itdb_Track *)track1;
Itdb_Track *t2 = (Itdb_Track *)track2;
if(t1->track_nr != t2->track_nr)
return t1->track_nr - t2->track_nr;
return strcasecmp(t1->title, t2->title);
}
};
class IpodMediaItem : public MediaItem
{
public:
IpodMediaItem( TQListView *parent, MediaDevice *dev )
: MediaItem( parent ) { init( dev ); }
IpodMediaItem( TQListViewItem *parent, MediaDevice *dev )
: MediaItem( parent ) { init( dev ); }
IpodMediaItem( TQListView *parent, TQListViewItem *after, MediaDevice *dev )
: MediaItem( parent, after ) { init( dev ); }
IpodMediaItem( TQListViewItem *parent, TQListViewItem *after, MediaDevice *dev )
: MediaItem( parent, after ) { init( dev ); }
virtual ~IpodMediaItem() { delete m_podcastInfo; }
void init( MediaDevice *dev )
{
m_track = 0;
m_playlist = 0;
m_device = dev;
m_podcastInfo = 0;
}
void bundleFromTrack( Itdb_Track *track, const TQString& path )
{
MetaBundle *bundle = new MetaBundle();
bundle->setArtist ( TQString::fromUtf8( track->artist ) );
bundle->setComposer ( TQString::fromUtf8( track->composer ) );
bundle->setAlbum ( TQString::fromUtf8( track->album ) );
bundle->setTitle ( TQString::fromUtf8( track->title ) );
bundle->setComment ( TQString::fromUtf8( track->comment ) );
bundle->setGenre ( TQString::fromUtf8( track->genre ) );
bundle->setYear ( track->year );
bundle->setTrack ( track->track_nr );
bundle->setDiscNumber( track->cd_nr );
bundle->setBpm ( track->BPM );
bundle->setLength ( track->tracklen/1000 );
bundle->setBitrate ( track->bitrate );
bundle->setSampleRate( track->samplerate );
bundle->setPath ( path );
bundle->setFilesize ( track->size );
TQString rss( track->podcastrss );
TQString url( track->podcasturl );
TQString desc( track->description );
TQString subtitle( track->subtitle );
TQDateTime date;
date.setTime_t( itdb_time_mac_to_host( track->time_released) );
if( !rss.isEmpty() || !url.isEmpty() )
{
PodcastEpisodeBundle peb( KURL::fromPathOrURL(url), KURL::fromPathOrURL(rss),
track->title, track->artist, desc, date.toString(Qt::ISODate), TQString() /*type*/,
bundle->length(), TQString() /*guid*/, track->playcount<=0 );
bundle->setPodcastBundle( peb );
}
setBundle( bundle );
}
Itdb_Track *m_track;
Itdb_Playlist *m_playlist;
PodcastInfo *m_podcastInfo;
int played() const { return m_track ? m_track->playcount : 0; }
int recentlyPlayed() const { return m_track ? m_track->recent_playcount : 0; }
int rating() const { return m_track ? m_track->rating : 0; }
void setRating( int rating )
{
if( m_track ) m_track->rating = m_track->app_rating = rating;
if( dynamic_cast<IpodMediaDevice *>(device()) )
static_cast<IpodMediaDevice *>(device())->m_dbChanged = true;
}
void setPlayCount( int playcount )
{
if ( m_track )
m_track->playcount = playcount;
if( dynamic_cast<IpodMediaDevice *>(device()) )
static_cast<IpodMediaDevice *>(device())->m_dbChanged = true;
}
void setLastPlayed( uint lastplay )
{
if ( m_track )
m_track->time_played = itdb_time_host_to_mac( lastplay );
if( dynamic_cast<IpodMediaDevice *>(device()) )
static_cast<IpodMediaDevice *>(device())->m_dbChanged = true;
}
bool ratingChanged() const { return m_track ? m_track->rating != m_track->app_rating : false; }
void setListened( bool l )
{
MediaItem::setListened( l );
if( type() == PODCASTITEM )
{
if( m_podcastInfo )
m_podcastInfo->listened = listened();
if( m_track )
m_track->mark_unplayed = listened() ? 0x01 : 0x02;
}
}
TQDateTime playTime() const
{
TQDateTime t;
if( m_track )
t.setTime_t( itdb_time_mac_to_host( m_track->time_played ) );
return t;
}
IpodMediaItem *findTrack( Itdb_Track *track )
{
if( m_track == track )
return this;
for( IpodMediaItem *it = dynamic_cast<IpodMediaItem *>( firstChild() );
it;
it = dynamic_cast<IpodMediaItem *>( it->nextSibling()) )
{
IpodMediaItem *found = it->findTrack(track);
if( found )
return found;
}
return 0;
}
};
IpodMediaDevice::IpodMediaDevice()
: MediaDevice()
, m_masterPlaylist( 0 )
, m_podcastPlaylist( 0 )
, m_lockFile( 0 )
, m_customAction( 0 )
{
registerTaglibPlugins();
m_podcastItem = 0;
m_staleItem = 0;
m_orphanedItem = 0;
m_invisibleItem = 0;
m_playlistItem = 0;
m_dbChanged = false;
m_itdb = 0;
m_podcastItem = 0;
m_staleItem = 0;
m_orphanedItem = 0;
m_invisibleItem = 0;
m_playlistItem = 0;
m_supportsArtwork = true;
m_supportsVideo = false;
m_rockboxFirmware = false;
m_isShuffle = false;
m_isMobile = false;
m_isIPhone = false;
m_needsFirewireGuid = false;
m_requireMount = true;
m_name = "iPod";
// config stuff
m_autoConnect = true;
m_syncStatsCheck = 0;
m_autoDeletePodcastsCheck = 0;
TDEActionCollection *ac = new TDEActionCollection( this );
TDEActionMenu *am = new TDEActionMenu( i18n( "iPod" ), Amarok::icon( "device" ), ac );
m_customAction = am;
m_customAction->setEnabled( false );
am->setDelayed( false );
TDEPopupMenu *menu = am->popupMenu();
connect( menu, TQT_SIGNAL(activated(int)), TQT_SLOT(slotIpodAction(int)) );
menu->insertItem( i18n( "Stale and Orphaned" ), CHECK_INTEGRITY );
menu->insertItem( i18n( "Update Artwork" ), UPDATE_ARTWORK );
TDEPopupMenu *ipodGen = new TDEPopupMenu( menu );
menu->insertItem( i18n( "Set iPod Model" ), ipodGen );
const Itdb_IpodInfo *table = itdb_info_get_ipod_info_table();
if( !table )
return;
bool infoFound = false;
int generation = ITDB_IPOD_GENERATION_FIRST;
do
{
const Itdb_IpodInfo *info = table;
infoFound = false;
TDEPopupMenu *gen = 0;
int index = SET_IPOD_MODEL;
while( info->model_number )
{
if( info->ipod_generation == generation )
{
if (!infoFound)
{
infoFound = true;
gen = new TDEPopupMenu( ipodGen );
connect( gen, TQT_SIGNAL(activated(int)), TQT_SLOT(slotIpodAction(int)) );
ipodGen->insertItem(
itdb_info_get_ipod_generation_string( info->ipod_generation),
gen );
}
if( info->capacity > 0.f )
gen->insertItem( i18n( "%1 GB %2 (x%3)" )
.arg( TQString::number( info->capacity ),
itdb_info_get_ipod_model_name_string( info->ipod_model ),
info->model_number ),
index );
else
gen->insertItem( i18n( "%1 (x%2)" )
.arg( itdb_info_get_ipod_model_name_string( info->ipod_model ),
info->model_number ),
index );
}
++info;
++index;
}
++generation;
}
while( infoFound );
}
void
IpodMediaDevice::slotIpodAction( int id )
{
switch( id )
{
case CHECK_INTEGRITY:
checkIntegrity();
break;
case UPDATE_ARTWORK:
updateArtwork();
break;
default:
if( const Itdb_IpodInfo *table = itdb_info_get_ipod_info_table() )
{
int index = id - SET_IPOD_MODEL;
if( m_itdb && m_itdb->device )
{
gchar model[PATH_MAX];
g_snprintf (model, PATH_MAX, "x%s", table[index].model_number);
itdb_device_set_sysinfo( m_itdb->device, "ModelNumStr", model );
detectModel();
if( m_isIPhone )
{
m_autoConnect = false;
setConfigBool( "AutoConnect", m_autoConnect );
}
// try to make sure that the Device directory exists
TQDir dir;
TQString realPath;
if(!pathExists( itunesDir(), &realPath) )
{
dir.setPath(realPath);
dir.mkdir(dir.absPath());
}
if(!pathExists( itunesDir( "Device" ), &realPath) )
{
dir.setPath(realPath);
dir.mkdir(dir.absPath());
}
GError *err = 0;
gboolean success = itdb_device_write_sysinfo(m_itdb->device, &err);
debug() << "success writing sysinfo to ipod? (return value " << success << ")" << endl;
if( !success && err )
{
g_error_free(err);
//FIXME: update i18n files for next message
Amarok::StatusBar::instance()->longMessage(
i18n( "Could not write SysInfo file to iPod (check the permissions of the file \"%1\" on your iPod)" ).arg( itunesDir( "Device:SysInfo" ) ) );
//FIXME: update i18n files for next message
Amarok::StatusBar::instance()->shortMessage(
i18n( "Unable to set iPod model to %1 GB %2 (x%3)" )
.arg( TQString::number( table[index].capacity ),
itdb_info_get_ipod_model_name_string( table[index].ipod_model ),
table[index].model_number ) );
}
else
{
Amarok::StatusBar::instance()->shortMessage(
i18n( "Setting iPod model to %1 GB %2 (x%3)" )
.arg( TQString::number( table[index].capacity ),
itdb_info_get_ipod_model_name_string( table[index].ipod_model ),
table[index].model_number ) );
}
MediaBrowser::instance()->updateDevices();
}
}
break;
}
}
void
IpodMediaDevice::init( MediaBrowser* parent )
{
MediaDevice::init( parent );
}
IpodMediaDevice::~IpodMediaDevice()
{
if( m_itdb )
itdb_free(m_itdb);
m_files.clear();
}
bool
IpodMediaDevice::isConnected()
{
return ( m_itdb != 0 );
}
MediaItem *
IpodMediaDevice::insertTrackIntoDB( const TQString &pathname,
const MetaBundle &metaBundle, const MetaBundle &propertiesBundle,
const PodcastInfo *podcastInfo )
{
return updateTrackInDB( 0, pathname, metaBundle, propertiesBundle, podcastInfo );
}
MediaItem *
IpodMediaDevice::updateTrackInDB( IpodMediaItem *item, const TQString &pathname,
const MetaBundle &metaBundle, const MetaBundle &propertiesBundle,
const PodcastInfo *podcastInfo )
{
if( !m_itdb )
return 0;
Itdb_Track *track = 0;
if( item )
track = item->m_track;
if( !track )
track = itdb_track_new();
if( !track )
{
delete item;
return 0;
}
TQString type = TQString(pathname.section('.', -1)).lower();
track->ipod_path = g_strdup( ipodPath(pathname).latin1() );
debug() << "on iPod: " << track->ipod_path << ", podcast=" << podcastInfo << endl;
if( metaBundle.isValidMedia() || !metaBundle.title().isEmpty() )
track->title = g_strdup( metaBundle.title().utf8() );
else
track->title = g_strdup( metaBundle.url().filename().utf8() );
track->album = g_strdup( metaBundle.album()->utf8() );
track->artist = g_strdup( metaBundle.artist()->utf8() );
track->genre = g_strdup( metaBundle.genre()->utf8() );
track->mediatype = ITDB_MEDIATYPE_AUDIO;
bool audiobook = false;
if(type=="wav")
{
track->filetype = g_strdup( "wav" );
}
else if(type=="mp3" || type=="mpeg")
{
track->filetype = g_strdup( "mpeg" );
}
else if(type=="aac" || type=="m4a" || (!m_supportsVideo && type=="mp4"))
{
track->filetype = g_strdup( "mp4" );
}
else if(type=="m4b")
{
audiobook = true;
track->filetype = g_strdup( "mp4" );
}
else if(type=="m4v" || type=="mp4v" || type=="mov" || type=="mpg" || type=="mp4")
{
track->filetype = g_strdup( "m4v video" );
track->movie_flag = 0x01; // for videos
track->mediatype = ITDB_MEDIATYPE_MOVIE;
}
else if(type=="aa")
{
audiobook = true;
track->filetype = g_strdup( "audible" );
TagLib::Audible::File f( TQFile::encodeName( propertiesBundle.url().path() ) );
TagLib::Audible::Tag *t = f.getAudibleTag();
if( t )
track->drm_userid = t->userID();
// libgpod also tries to set those, but this won't work
track->unk126 = 0x01;
track->unk144 = 0x0029;
}
else
{
track->filetype = g_strdup( type.utf8() );
}
TQString genre = metaBundle.genre();
if( genre.startsWith("audiobook", false) )
audiobook = true;
if( audiobook )
{
track->remember_playback_position |= 0x01;
track->skip_when_shuffling |= 0x01;
track->mediatype = ITDB_MEDIATYPE_AUDIOBOOK;
}
track->composer = g_strdup( metaBundle.composer()->utf8() );
track->comment = g_strdup( metaBundle.comment()->utf8() );
track->track_nr = metaBundle.track();
track->cd_nr = metaBundle.discNumber();
track->BPM = static_cast<int>( metaBundle.bpm() );
track->year = metaBundle.year();
track->size = propertiesBundle.filesize();
if( track->size == 0 )
{
debug() << "filesize is zero for " << track->ipod_path << ", expect strange problems with your ipod" << endl;
}
track->bitrate = propertiesBundle.bitrate();
track->samplerate = propertiesBundle.sampleRate();
track->tracklen = propertiesBundle.length()*1000;
//Get the createdate from database
QueryBuilder qb;
qb.addReturnValue( QueryBuilder::tabSong, QueryBuilder::valCreateDate );
qb.addMatch( QueryBuilder::tabSong, QueryBuilder::valURL, metaBundle.url().path() );
TQStringList values = qb.run();
//Add to track info if present
if ( values.count() ) {
uint createdate = values.first().toUInt();
track->time_added = itdb_time_host_to_mac( createdate );
track->time_modified = itdb_time_host_to_mac( createdate );
}
if(podcastInfo)
{
track->skip_when_shuffling = 0x01; // skip when shuffling
track->remember_playback_position = 0x01; // remember playback position
// FIXME: track->unk176 = 0x00020000; // for podcasts
track->mark_unplayed = podcastInfo->listened ? 0x01 : 0x02;
track->mediatype =
track->mediatype==ITDB_MEDIATYPE_MOVIE
? ITDB_MEDIATYPE_PODCAST | ITDB_MEDIATYPE_MOVIE
: ITDB_MEDIATYPE_PODCAST;
track->flag4 = 0x01; // also show description on iPod
TQString plaindesc = podcastInfo->description;
plaindesc.replace( TQRegExp("<[^>]*>"), "" );
track->description = g_strdup( plaindesc.utf8() );
track->subtitle = g_strdup( plaindesc.utf8() );
track->podcasturl = g_strdup( podcastInfo->url.utf8() );
track->podcastrss = g_strdup( podcastInfo->rss.utf8() );
//track->category = g_strdup( i18n( "Unknown" ) );
track->time_released = itdb_time_host_to_mac( podcastInfo->date.toTime_t() );
//track->compilation = 0x01; // this should have made the ipod play a sequence of podcasts
}
else
{
if( metaBundle.compilation() == MetaBundle::CompilationYes )
{
track->compilation = 0x01;
}
else
{
track->compilation = 0x00;
}
}
m_dbChanged = true;
if( m_supportsArtwork )
{
TQString image;
if( metaBundle.podcastBundle() )
{
PodcastChannelBundle pcb;
if( CollectionDB::instance()->getPodcastChannelBundle( metaBundle.podcastBundle()->parent(), &pcb ) )
image = CollectionDB::instance()->podcastImage( pcb.imageURL().url(), 0 );
}
if( image.isEmpty() )
image = CollectionDB::instance()->albumImage(metaBundle.artist(), metaBundle.album(), false, 0);
if( !image.endsWith( "@nocover.png" ) )
{
debug() << "adding image " << image << " to " << metaBundle.artist() << ":" << metaBundle.album() << endl;
itdb_track_set_thumbnails( track, g_strdup( TQFile::encodeName(image) ) );
}
}
if( item )
{
MediaItem *parent = dynamic_cast<MediaItem *>(item->parent());
if( parent )
{
parent->takeItem( item );
if( parent->childCount() == 0 && !isSpecialItem( parent ) )
{
MediaItem *pp = dynamic_cast<MediaItem *>(parent->parent());
delete parent;
if( pp && pp->childCount() == 0 && !isSpecialItem( pp ) )
delete pp;
}
}
}
else
{
itdb_track_add(m_itdb, track, -1);
if(podcastInfo)
{
Itdb_Playlist *podcasts = itdb_playlist_podcasts(m_itdb);
if(!podcasts)
{
podcasts = itdb_playlist_new("Podcasts", false);
itdb_playlist_add(m_itdb, podcasts, -1);
itdb_playlist_set_podcasts(podcasts);
addPlaylistToView( podcasts );
}
itdb_playlist_add_track(podcasts, track, -1);
}
else
{
// gtkpod 0.94 does not like if not all songs in the db are on the master playlist
// but we try anyway
Itdb_Playlist *mpl = itdb_playlist_mpl(m_itdb);
if( !mpl )
{
mpl = itdb_playlist_new( "iPod", false );
itdb_playlist_add( m_itdb, mpl, -1 );
itdb_playlist_set_mpl( mpl );
addPlaylistToView( mpl );
}
itdb_playlist_add_track(mpl, track, -1);
}
}
return addTrackToView( track, item );
}
MediaItem *
IpodMediaDevice::copyTrackToDevice(const MetaBundle &bundle)
{
KURL url = determineURLOnDevice(bundle);
// check if path exists and make it if needed
TQFileInfo finfo( url.path() );
TQDir dir = finfo.dir();
while ( !dir.exists() )
{
TQString path = dir.absPath();
TQDir parentdir;
TQDir create;
do
{
create.setPath(path);
path = path.section("/", 0, path.contains('/')-1);
parentdir.setPath(path);
}
while( !path.isEmpty() && !(path==mountPoint()) && !parentdir.exists() );
debug() << "trying to create \"" << path << "\"" << endl;
if(!create.mkdir( create.absPath() ))
{
break;
}
}
if ( !dir.exists() )
{
Amarok::StatusBar::instance()->longMessage(
i18n( "Media Device: Creating directory for file %1 failed" ).arg( url.path() ),
KDE::StatusBar::Error );
return NULL;
}
if( !kioCopyTrack( bundle.url(), url ) )
{
return NULL;
}
PodcastInfo *podcastInfo = 0;
if( bundle.podcastBundle() )
{
PodcastEpisodeBundle *peb = bundle.podcastBundle();
podcastInfo = new PodcastInfo;
podcastInfo->url = peb->url().url();
podcastInfo->description = peb->description();
podcastInfo->author = peb->author();
podcastInfo->rss = peb->parent().url();
podcastInfo->date = peb->dateTime();
podcastInfo->listened = !peb->isNew();
}
MetaBundle propertiesBundle( url );
MediaItem *ret = insertTrackIntoDB( url.path(), bundle, propertiesBundle, podcastInfo );
delete podcastInfo;
return ret;
}
MediaItem *
IpodMediaDevice::tagsChanged( MediaItem *item, const MetaBundle &bundle )
{
return updateTrackInDB( dynamic_cast<IpodMediaItem *>(item), item->url().path(), bundle, bundle, NULL );
}
void
IpodMediaDevice::synchronizeDevice()
{
#if 1
debug() << "Syncing iPod!" << endl;
Amarok::StatusBar::instance()->newProgressOperation( this )
.setDescription( i18n( "Flushing iPod filesystem transfer cache" ) )
.setTotalSteps( 1 );
writeITunesDB();
Amarok::StatusBar::instance()->endProgressOperation( this );
#else
m_dbChanged = true;
debug() << "Deferring sync of iPod!" << endl;
#endif
}
MediaItem *
IpodMediaDevice::trackExists( const MetaBundle& bundle )
{
return getTrack( bundle.artist(),
bundle.album(),
bundle.title(),
bundle.discNumber(),
bundle.track(),
bundle.podcastBundle() );
}
MediaItem *
IpodMediaDevice::newPlaylist(const TQString &name, MediaItem *parent, TQPtrList<MediaItem> items)
{
m_dbChanged = true;
IpodMediaItem *item = new IpodMediaItem(parent, this);
item->setType(MediaItem::PLAYLIST);
item->setText(0, name);
addToPlaylist(item, 0, items);
return item;
}
void
IpodMediaDevice::addToPlaylist(MediaItem *mlist, MediaItem *after, TQPtrList<MediaItem> items)
{
IpodMediaItem *list = dynamic_cast<IpodMediaItem *>(mlist);
if(!list)
return;
m_dbChanged = true;
if(list->m_playlist)
{
itdb_playlist_remove(list->m_playlist);
list->m_playlist = 0;
}
int order;
IpodMediaItem *it;
if(after)
{
order = after->m_order + 1;
it = dynamic_cast<IpodMediaItem*>(after->nextSibling());
}
else
{
order = 0;
it = dynamic_cast<IpodMediaItem*>(list->firstChild());
}
for( ; it; it = dynamic_cast<IpodMediaItem *>(it->nextSibling()))
{
it->m_order += items.count();
}
for(IpodMediaItem *it = dynamic_cast<IpodMediaItem *>(items.first());
it;
it = dynamic_cast<IpodMediaItem *>(items.next()) )
{
if(!it->m_track)
continue;
IpodMediaItem *add;
if(it->parent() == list)
{
add = it;
if(after)
{
it->moveItem(after);
}
else
{
list->takeItem(it);
list->insertItem(it);
}
}
else
{
if(after)
{
add = new IpodMediaItem(list, after, this);
}
else
{
add = new IpodMediaItem(list, this);
}
}
after = add;
add->setType(MediaItem::PLAYLISTITEM);
add->m_track = it->m_track;
add->bundleFromTrack( add->m_track, realPath(add->m_track->ipod_path) );
add->setText(0, TQString::fromUtf8(it->m_track->artist) + " - " + TQString::fromUtf8(it->m_track->title) );
add->m_order = order;
order++;
}
// make numbering consecutive
int i=0;
for(IpodMediaItem *it = dynamic_cast<IpodMediaItem *>(list->firstChild());
it;
it = dynamic_cast<IpodMediaItem *>(it->nextSibling()))
{
it->m_order = i;
i++;
}
playlistFromItem(list);
}
int
IpodMediaDevice::deleteItemFromDevice(MediaItem *mediaitem, int flags )
{
IpodMediaItem *item = dynamic_cast<IpodMediaItem *>(mediaitem);
if(!item)
return -1;
if( isCanceled() )
return 0;
if( !item->isVisible() )
return 0;
int count = 0;
switch(item->type())
{
case MediaItem::PLAYLISTITEM:
if( !(flags & DeleteTrack) )
{
// FIXME possibly wrong instance of track is removed
itdb_playlist_remove_track(item->m_playlist, item->m_track);
delete item;
m_dbChanged = true;
break;
}
// else fall through
case MediaItem::STALE:
case MediaItem::TRACK:
case MediaItem::INVISIBLE:
case MediaItem::PODCASTITEM:
if(!(flags & OnlyPlayed) || item->played() > 0)
{
bool stale = item->type()==MediaItem::STALE;
Itdb_Track *track = item->m_track;
delete item;
// delete from playlists
for( IpodMediaItem *it = static_cast<IpodMediaItem *>(m_playlistItem)->findTrack(track);
it;
it = static_cast<IpodMediaItem *>(m_playlistItem)->findTrack(track) )
{
delete it;
}
// delete all other occurrences
for( IpodMediaItem *it = getTrack( track );
it;
it = getTrack( track ) )
{
delete it;
}
if( !stale )
{
// delete file
KURL url;
url.setPath(realPath(track->ipod_path));
deleteFile( url );
count++;
}
// remove from database
if( !removeDBTrack(track) )
count = -1;
}
break;
case MediaItem::ORPHANED:
deleteFile( item->url() );
delete item;
if( count >= 0 )
count++;
break;
case MediaItem::PLAYLISTSROOT:
case MediaItem::PODCASTSROOT:
case MediaItem::INVISIBLEROOT:
case MediaItem::STALEROOT:
case MediaItem::ORPHANEDROOT:
case MediaItem::ARTIST:
case MediaItem::ALBUM:
case MediaItem::PODCASTCHANNEL:
case MediaItem::PLAYLIST:
// just recurse
{
IpodMediaItem *next = 0;
for(IpodMediaItem *it = dynamic_cast<IpodMediaItem *>(item->firstChild());
it;
it = next)
{
if( isCanceled() )
break;
next = dynamic_cast<IpodMediaItem *>(it->nextSibling());
int ret = deleteItemFromDevice(it, flags);
if( ret >= 0 && count >= 0 )
count += ret;
else
count = -1;
}
}
if(item->type() == MediaItem::PLAYLIST && !isCanceled())
{
m_dbChanged = true;
itdb_playlist_remove(item->m_playlist);
}
if(item->type() != MediaItem::PLAYLISTSROOT
&& item->type() != MediaItem::PODCASTSROOT
&& item->type() != MediaItem::INVISIBLEROOT
&& item->type() != MediaItem::STALEROOT
&& item->type() != MediaItem::ORPHANEDROOT)
{
if(!(flags & OnlyPlayed) || item->played() > 0 || item->childCount() == 0)
{
if(item->childCount() > 0)
debug() << "recursive deletion should have removed all children from " << item << "(" << item->text(0) << ")" << endl;
else
delete item;
}
}
break;
case MediaItem::DIRECTORY:
case MediaItem::UNKNOWN:
// this should not happen
count = -1;
break;
}
updateRootItems();
return count;
}
bool
IpodMediaDevice::createLockFile( bool silent )
{
TQString lockFilePath;
pathExists( itunesDir( "iTunes:iTunesLock" ), &lockFilePath );
m_lockFile = new TQFile( lockFilePath );
TQString msg;
bool ok = true;
if( m_lockFile->exists() )
{
ok = false;
msg = i18n( "Media Device: iPod mounted at %1 already locked. " ).arg( mountPoint() );
msg += i18n( "If you are sure that this is an error, then remove the file %1 and try again." )
.arg( lockFilePath );
if( !silent )
{
if( KMessageBox::warningContinueCancel( m_parent, msg, i18n( "Remove iTunes Lock File?" ),
KGuiItem(i18n("&Remove"), "edit-delete"), TQString(), KMessageBox::Dangerous )
== KMessageBox::Continue )
{
msg = i18n( "Media Device: removing lockfile %1 failed: %2. " )
.arg( lockFilePath, m_lockFile->errorString() );
ok = m_lockFile->remove();
}
else
{
msg = "";
}
}
}
if( ok && !m_lockFile->open( IO_WriteOnly ) )
{
ok = false;
msg = i18n( "Media Device: failed to create lockfile on iPod mounted at %1: %2" )
.arg(mountPoint(), m_lockFile->errorString());
}
if( ok )
return true;
delete m_lockFile;
m_lockFile = 0;
if( !msg.isEmpty() )
Amarok::StatusBar::instance()->longMessage( msg, KDE::StatusBar::Sorry );
return false;
}
bool
IpodMediaDevice::initializeIpod()
{
TQDir dir( mountPoint() );
if( !dir.exists() )
{
Amarok::StatusBar::instance()->longMessage(
i18n("Media device: Mount point %1 does not exist").arg(mountPoint()),
KDE::StatusBar::Error );
return false;
}
debug() << "initializing iPod mounted at " << mountPoint() << endl;
// initialize iPod
m_itdb = itdb_new();
if( m_itdb == 0 )
return false;
// in order to get directories right
detectModel();
itdb_set_mountpoint(m_itdb, TQFile::encodeName(mountPoint()));
Itdb_Playlist *mpl = itdb_playlist_new("iPod", false);
itdb_playlist_set_mpl(mpl);
Itdb_Playlist *podcasts = itdb_playlist_new("Podcasts", false);
itdb_playlist_set_podcasts(podcasts);
itdb_playlist_add(m_itdb, podcasts, -1);
itdb_playlist_add(m_itdb, mpl, 0);
TQString realPath;
if(!pathExists( itunesDir(), &realPath) )
{
dir.setPath(realPath);
dir.mkdir(dir.absPath());
}
if(!dir.exists())
return false;
if(!pathExists( itunesDir( "Music" ), &realPath) )
{
dir.setPath(realPath);
dir.mkdir(dir.absPath());
}
if(!dir.exists())
return false;
if(!pathExists( itunesDir( "iTunes" ), &realPath) )
{
dir.setPath(realPath);
dir.mkdir(dir.absPath());
}
if(!dir.exists())
return false;
if( !writeITunesDB( false ) )
return false;
Amarok::StatusBar::instance()->longMessage(
i18n("Media Device: Initialized iPod mounted at %1").arg(mountPoint()),
KDE::StatusBar::Information );
return true;
}
bool
IpodMediaDevice::openDevice( bool silent )
{
m_isShuffle = false;
m_isMobile = false;
m_isIPhone = false;
m_supportsArtwork = false;
m_supportsVideo = false;
m_needsFirewireGuid = false;
m_rockboxFirmware = false;
m_dbChanged = false;
m_files.clear();
if( m_itdb )
{
Amarok::StatusBar::instance()->longMessage(
i18n("Media Device: iPod at %1 already opened").arg(mountPoint()),
KDE::StatusBar::Sorry );
return false;
}
// try to find a mounted ipod
bool ipodFound = false;
bool canInitialize = false;
KMountPoint::List currentmountpoints = KMountPoint::currentMountPoints();
for( KMountPoint::List::Iterator mountiter = currentmountpoints.begin();
mountiter != currentmountpoints.end();
++mountiter )
{
canInitialize = false;
TQString devicenode = (*mountiter)->mountedFrom();
TQString mountpoint = (*mountiter)->mountPoint();
if( mountpoint.startsWith( "/proc" ) ||
mountpoint.startsWith( "/sys" ) ||
mountpoint.startsWith( "/dev" ) ||
mountpoint.startsWith( "/boot" ) )
continue;
if( !mountPoint().isEmpty() )
{
if( mountpoint != mountPoint() )
continue;
canInitialize = true;
}
else if( !deviceNode().isEmpty() )
{
if( devicenode != deviceNode() )
continue;
canInitialize = true;
}
GError *err = 0;
m_itdb = itdb_parse(TQFile::encodeName(mountpoint), &err);
if( err )
{
g_error_free(err);
if( m_itdb )
{
itdb_free( m_itdb );
m_itdb = 0;
}
if( !canInitialize )
continue;
}
if( mountPoint().isEmpty() )
m_medium.setMountPoint( mountpoint );
ipodFound = true;
break;
}
if( !ipodFound && !canInitialize )
{
if( !silent )
{
Amarok::StatusBar::instance()->longMessage(
i18n("Media Device: No mounted iPod found" ),
KDE::StatusBar::Sorry );
}
return false;
}
if( !m_itdb && canInitialize )
{
TQString msg = i18n( "Media Device: could not find iTunesDB on device mounted at %1. "
"Should I try to initialize your iPod?" ).arg( mountPoint() );
if( !silent
&& KMessageBox::warningContinueCancel( m_parent, msg, i18n( "Initialize iPod?" ),
KGuiItem(i18n("&Initialize"), "new") ) == KMessageBox::Continue )
{
if( !initializeIpod() )
{
if( m_itdb )
{
itdb_free( m_itdb );
m_itdb = 0;
}
Amarok::StatusBar::instance()->longMessage(
i18n("Media Device: Failed to initialize iPod mounted at %1").arg(mountPoint()),
KDE::StatusBar::Sorry );
return false;
}
}
else
return false;
}
detectModel();
if( !createLockFile( silent ) )
{
if( m_itdb )
{
itdb_free( m_itdb );
m_itdb = 0;
}
return false;
}
for( int i=0; i < itdb_musicdirs_number(m_itdb); i++)
{
TQString real;
TQString ipod;
ipod.sprintf( itunesDir( "Music:f%02d" ).latin1(), i );
if(!pathExists( ipod, &real ) )
{
TQDir dir( real );
dir.mkdir( real );
dir.setPath( real );
if( !dir.exists() )
{
debug() << "failed to create hash dir " << real << endl;
Amarok::StatusBar::instance()->longMessage(
i18n("Media device: Failed to create directory %1").arg(real),
KDE::StatusBar::Error );
return false;
}
}
}
if( !silent )
kapp->processEvents( 100 );
initView();
GList *cur = m_itdb->playlists;
for( ; cur; cur = cur->next )
{
Itdb_Playlist *playlist = (Itdb_Playlist *)cur->data;
addPlaylistToView( playlist );
}
if( !silent )
kapp->processEvents( 100 );
for( cur = m_itdb->tracks; cur; cur = cur->next )
{
Itdb_Track *track = (Itdb_Track *)cur->data;
addTrackToView( track, 0 /*parent*/, false /*checkintegrity*/, true /*batchmode*/ );
}
if( !silent )
kapp->processEvents( 100 );
updateRootItems();
m_customAction->setEnabled( true );
m_dbChanged = true; // write at least once for synchronising new stats
return true;
}
void
IpodMediaDevice::detectModel()
{
// set some sane default values
m_isShuffle = false;
m_supportsArtwork = true;
m_supportsVideo = false;
m_isIPhone = false;
m_needsFirewireGuid = false;
m_rockboxFirmware = false;
// needs recent libgpod-0.3.3 from cvs
bool guess = false;
if( m_itdb && m_itdb->device )
{
const Itdb_IpodInfo *ipodInfo = itdb_device_get_ipod_info( m_itdb->device );
const gchar *modelString = 0;
m_supportsArtwork = itdb_device_supports_artwork( m_itdb->device );
if( ipodInfo )
{
modelString = itdb_info_get_ipod_model_name_string ( ipodInfo->ipod_model );
switch( ipodInfo->ipod_model )
{
case ITDB_IPOD_MODEL_SHUFFLE:
#ifdef HAVE_LIBGPOD_060
case ITDB_IPOD_MODEL_SHUFFLE_SILVER:
case ITDB_IPOD_MODEL_SHUFFLE_PINK:
case ITDB_IPOD_MODEL_SHUFFLE_BLUE:
case ITDB_IPOD_MODEL_SHUFFLE_GREEN:
case ITDB_IPOD_MODEL_SHUFFLE_ORANGE:
case ITDB_IPOD_MODEL_SHUFFLE_PURPLE:
#endif
m_isShuffle = true;
break;
#ifdef HAVE_LIBGPOD_060
case ITDB_IPOD_MODEL_IPHONE_1:
case ITDB_IPOD_GENERATION_TOUCH_1:
m_isIPhone = true;
debug() << "detected iPhone/iPod Touch" << endl;
break;
case ITDB_IPOD_MODEL_CLASSIC_SILVER:
case ITDB_IPOD_MODEL_CLASSIC_BLACK:
#endif
case ITDB_IPOD_MODEL_VIDEO_WHITE:
case ITDB_IPOD_MODEL_VIDEO_BLACK:
case ITDB_IPOD_MODEL_VIDEO_U2:
m_supportsVideo = true;
debug() << "detected video-capable iPod" << endl;
break;
case ITDB_IPOD_MODEL_MOBILE_1:
m_isMobile = true;
m_supportsArtwork = true;
debug() << "detected iTunes phone" << endl;
break;
case ITDB_IPOD_MODEL_INVALID:
case ITDB_IPOD_MODEL_UNKNOWN:
modelString = 0;
guess = true;
break;
default:
break;
}
#ifdef HAVE_LIBGPOD_060
switch( ipodInfo->ipod_generation )
{
case ITDB_IPOD_GENERATION_CLASSIC_1:
case ITDB_IPOD_GENERATION_NANO_3:
case ITDB_IPOD_GENERATION_TOUCH_1:
m_needsFirewireGuid = true;
m_supportsVideo = true;
break;
case ITDB_IPOD_GENERATION_VIDEO_1:
case ITDB_IPOD_GENERATION_VIDEO_2:
m_supportsVideo = true;
break;
case ITDB_IPOD_GENERATION_SHUFFLE_1:
case ITDB_IPOD_GENERATION_SHUFFLE_2:
case ITDB_IPOD_GENERATION_SHUFFLE_3:
m_isShuffle = true;
break;
default:
break;
}
#endif
}
if( modelString )
m_name = TQString( "iPod %1" ).arg( TQString::fromUtf8( modelString ) );
if( m_needsFirewireGuid )
{
gchar *fwid = itdb_device_get_sysinfo( m_itdb->device, "FirewireGuid" );
if( !fwid )
{
Amarok::StatusBar::instance()->longMessage(
i18n("Your iPod's Firewire GUID is required for correctly updating its music database, but it is not known. See %1 for more information.").arg( "http://amarok.kde.org/wiki/Media_Device:IPod" ) );
}
else
g_free( fwid );
}
}
else
{
debug() << "iPod type detection failed, no video support" << endl;
Amarok::StatusBar::instance()->longMessage(
i18n("iPod type detection failed: no support for iPod Shuffle, for artwork or video") );
guess = true;
}
if( guess )
{
if( pathExists( ":iTunes:iTunes_Control" ) )
{
debug() << "iTunes/iTunes_Control found - assuming itunes phone" << endl;
m_isMobile = true;
}
else if( pathExists( ":iTunes_Control" ) )
{
debug() << "iTunes_Control found - assuming iPhone/iPod Touch" << endl;
m_isIPhone = true;
}
}
if( m_isIPhone )
{
m_supportsVideo = true;
m_supportsArtwork = true;
}
if( pathExists( ":.rockbox" ) )
{
debug() << "RockBox firmware detected" << endl;
m_rockboxFirmware = true;
}
}
void
IpodMediaDevice::initView()
{
m_view->clear();
m_playlistItem = new IpodMediaItem( m_view, this );
m_playlistItem->setText( 0, i18n("Playlists") );
m_playlistItem->m_order = -6;
m_playlistItem->setType( MediaItem::PLAYLISTSROOT );
m_podcastItem = new IpodMediaItem( m_view, this );
m_podcastItem->setText( 0, i18n("Podcasts") );
m_podcastItem->m_order = -5;
m_podcastItem->setType( MediaItem::PODCASTSROOT );
m_invisibleItem = new IpodMediaItem( m_view, this );
m_invisibleItem->setText( 0, i18n("Invisible") );
m_invisibleItem->m_order = -4;
m_invisibleItem->setType( MediaItem::INVISIBLEROOT );
m_staleItem = new IpodMediaItem( m_view, this );
m_staleItem->setText( 0, i18n("Stale") );
m_staleItem->m_order = -3;
m_staleItem->setType( MediaItem::STALEROOT );
m_orphanedItem = new IpodMediaItem( m_view, this );
m_orphanedItem->setText( 0, i18n("Orphaned") );
m_orphanedItem->m_order = -2;
m_orphanedItem->setType( MediaItem::ORPHANEDROOT );
updateRootItems();
}
void
IpodMediaDevice::updateArtwork()
{
if( !m_supportsArtwork )
return;
TQPtrList<MediaItem> items;
m_view->getSelectedLeaves( 0, &items, false );
int updateCount = 0;
for( TQPtrList<MediaItem>::iterator it = items.begin();
it != items.end();
it++ )
{
IpodMediaItem *i = dynamic_cast<IpodMediaItem *>( *it );
if( !i || i->type() == MediaItem::PLAYLISTITEM )
continue;
const MetaBundle *bundle = i->bundle();
TQString image;
if( i->m_podcastInfo && !i->m_podcastInfo->rss.isEmpty() )
{
PodcastChannelBundle pcb;
if( CollectionDB::instance()->getPodcastChannelBundle( i->m_podcastInfo->rss, &pcb ) )
image = CollectionDB::instance()->podcastImage( pcb.imageURL().url(), 0 );
}
if( image.isEmpty() )
image = CollectionDB::instance()->albumImage(bundle->artist(), bundle->album(), false, 0);
if( !image.endsWith( "@nocover.png" ) )
{
debug() << "adding image " << image << " to " << bundle->artist() << ":"
<< bundle->album() << endl;
itdb_track_set_thumbnails( i->m_track, g_strdup( TQFile::encodeName(image) ) );
++updateCount;
}
}
Amarok::StatusBar::instance()->shortMessage(
i18n( "Updated artwork for one track", "Updated artwork for %n tracks", updateCount ) );
if(!m_dbChanged)
m_dbChanged = updateCount > 0;
}
bool
IpodMediaDevice::checkIntegrity()
{
if( !m_itdb )
return false;
initView();
GList *cur = m_itdb->tracks;
while(cur)
{
Itdb_Track *track = (Itdb_Track *)cur->data;
addTrackToView( track, 0, true );
cur = cur->next;
}
cur = m_itdb->playlists;
for( ; cur; cur = cur->next )
{
Itdb_Playlist *playlist = (Itdb_Playlist *)cur->data;
addPlaylistToView( playlist );
}
TQString musicpath;
if (!pathExists( itunesDir( "Music" ), &musicpath ))
return false;
TQDir dir( musicpath, TQString(), TQDir::Unsorted, TQDir::Dirs );
for(unsigned i=0; i<dir.count(); i++)
{
if(dir[i] == "." || dir[i] == "..")
continue;
TQString hashpath = musicpath + '/' + dir[i];
TQDir hashdir( hashpath, TQString(), TQDir::Unsorted, TQDir::Files );
for(unsigned j=0; j<hashdir.count(); j++)
{
TQString filename = hashpath + '/' + hashdir[j];
TQString ipodPath = itunesDir( "Music:" ) + dir[i] + ':' + hashdir[j];
Itdb_Track *track = m_files[ipodPath.lower()];
if(!track)
{
debug() << "file: " << filename << " is orphaned" << endl;
IpodMediaItem *item = new IpodMediaItem(m_orphanedItem, this);
item->setType(MediaItem::ORPHANED);
KURL url = KURL::fromPathOrURL(filename);
MetaBundle *bundle = new MetaBundle(url);
item->setBundle( bundle );
TQString title = bundle->artist() + " - " + bundle->title();
item->setText(0, title);
}
}
}
updateRootItems();
Amarok::StatusBar::instance()->shortMessage(
i18n( "Scanning for stale and orphaned tracks finished" ) );
return true;
}
bool
IpodMediaDevice::closeDevice() //SLOT
{
m_customAction->setEnabled( false );
writeITunesDB();
m_view->clear();
m_podcastItem = 0;
m_playlistItem = 0;
m_orphanedItem = 0;
m_staleItem = 0;
m_invisibleItem = 0;
if( m_lockFile )
{
m_lockFile->remove();
m_lockFile->close();
delete m_lockFile;
m_lockFile = 0;
}
m_files.clear();
itdb_free(m_itdb);
m_itdb = 0;
m_masterPlaylist = 0;
m_podcastPlaylist = 0;
m_name = "iPod";
return true;
}
void
IpodMediaDevice::renameItem( TQListViewItem *i ) // SLOT
{
IpodMediaItem *item = dynamic_cast<IpodMediaItem *>(i);
if(!item)
return;
if(!item->type() == MediaItem::PLAYLIST)
return;
m_dbChanged = true;
g_free(item->m_playlist->name);
item->m_playlist->name = g_strdup( item->text( 0 ).utf8() );
}
void
IpodMediaDevice::playlistFromItem(IpodMediaItem *item)
{
if( !m_itdb )
return;
m_dbChanged = true;
item->m_playlist = itdb_playlist_new(item->text(0).utf8(), false /* dumb playlist */ );
itdb_playlist_add(m_itdb, item->m_playlist, -1);
for(IpodMediaItem *it = dynamic_cast<IpodMediaItem *>(item->firstChild());
it;
it = dynamic_cast<IpodMediaItem *>(it->nextSibling()) )
{
itdb_playlist_add_track(item->m_playlist, it->m_track, -1);
it->m_playlist = item->m_playlist;
}
}
IpodMediaItem *
IpodMediaDevice::addTrackToView(Itdb_Track *track, IpodMediaItem *item, bool checkIntegrity, bool batchmode )
{
bool visible = false;
bool stale = false;
if( checkIntegrity )
{
if( !pathExists( track->ipod_path ) )
{
stale = true;
debug() << "track: " << track->artist << " - " << track->album << " - " << track->title << " is stale: " << track->ipod_path << " does not exist" << endl;
if( item )
m_staleItem->insertItem( item );
else
item = new IpodMediaItem(m_staleItem, this);
item->setType(MediaItem::STALE);
TQString title = TQString::fromUtf8(track->artist) + " - "
+ TQString::fromUtf8(track->title);
item->setText( 0, title );
item->m_track = track;
}
else
{
m_files.insert( TQString(track->ipod_path).lower(), track );
}
}
if(!stale && m_masterPlaylist && itdb_playlist_contains_track(m_masterPlaylist, track)
&& (!m_podcastPlaylist || !itdb_playlist_contains_track(m_podcastPlaylist, track)))
{
visible = true;
TQString artistName;
if( track->compilation )
artistName = i18n( "Various Artists" );
else
artistName = TQString::fromUtf8(track->artist);
IpodMediaItem *artist = getArtist(artistName);
if(!artist)
{
artist = new IpodMediaItem(m_view, this);
artist->setText( 0, artistName );
artist->setType( MediaItem::ARTIST );
if( artistName == i18n( "Various Artists" ) )
artist->m_order = -1;
}
TQString albumName(TQString::fromUtf8(track->album));
MediaItem *album = artist->findItem(albumName);
if(!album)
{
album = new IpodMediaItem( artist, this );
album->setText( 0, albumName );
album->setType( MediaItem::ALBUM );
}
if( item )
album->insertItem( item );
else
{
item = new IpodMediaItem( album, this );
}
TQString titleName = TQString::fromUtf8(track->title);
if( track->compilation )
item->setText( 0, TQString::fromUtf8(track->artist) + i18n( " - " ) + titleName );
else
item->setText( 0, titleName );
item->setType( MediaItem::TRACK );
item->m_track = track;
item->bundleFromTrack( track, realPath(track->ipod_path) );
item->m_order = track->track_nr;
}
if(!stale && m_podcastPlaylist && itdb_playlist_contains_track(m_podcastPlaylist, track))
{
visible = true;
TQString channelName(TQString::fromUtf8(track->album));
IpodMediaItem *channel = dynamic_cast<IpodMediaItem *>(m_podcastItem->findItem(channelName));
if(!channel)
{
channel = new IpodMediaItem(m_podcastItem, this);
channel->setText( 0, channelName );
channel->setType( MediaItem::PODCASTCHANNEL );
channel->m_podcastInfo = new PodcastInfo;
}
if( item )
channel->insertItem( item );
else
item = new IpodMediaItem( channel, this );
item->setText( 0, TQString::fromUtf8(track->title) );
item->setType( MediaItem::PODCASTITEM );
item->m_track = track;
item->bundleFromTrack( track, realPath(track->ipod_path) );
PodcastInfo *info = new PodcastInfo;
item->m_podcastInfo = info;
info->url = TQString::fromUtf8( track->podcasturl );
info->rss = TQString::fromUtf8( track->podcastrss );
info->description = TQString::fromUtf8( track->description );
info->date.setTime_t( itdb_time_mac_to_host( track->time_released) );
if( !info->rss.isEmpty() && channel->m_podcastInfo->rss.isEmpty() )
{
channel->m_podcastInfo->rss = info->rss;
}
}
if( !stale && !visible )
{
debug() << "invisible, title=" << track->title << endl;
if( item )
m_invisibleItem->insertItem( item );
else
item = new IpodMediaItem(m_invisibleItem, this);
TQString title = TQString::fromUtf8(track->artist) + " - "
+ TQString::fromUtf8(track->title);
item->setText( 0, title );
item->setType( MediaItem::INVISIBLE );
item->m_track = track;
item->bundleFromTrack( track, realPath(track->ipod_path) );
}
if ( !batchmode )
updateRootItems();
return item;
}
void
IpodMediaDevice::addPlaylistToView( Itdb_Playlist *pl )
{
if( itdb_playlist_is_mpl( pl ) )
{
m_masterPlaylist = pl;
return;
}
if( itdb_playlist_is_podcasts( pl ) )
{
m_podcastPlaylist = pl;
return;
}
if( pl->is_spl )
{
debug() << "playlist " << pl->name << " is a smart playlist" << endl;
}
TQString name( TQString::fromUtf8(pl->name) );
IpodMediaItem *playlist = dynamic_cast<IpodMediaItem *>(m_playlistItem->findItem(name));
if( !playlist )
{
playlist = new IpodMediaItem( m_playlistItem, this );
playlist->setText( 0, name );
playlist->setType( MediaItem::PLAYLIST );
playlist->m_playlist = pl;
}
int i=0;
GList *cur = pl->members;
while( cur )
{
Itdb_Track *track = (Itdb_Track *)cur->data;
IpodMediaItem *item = new IpodMediaItem(playlist, this);
TQString title = TQString::fromUtf8(track->artist) + " - "
+ TQString::fromUtf8(track->title);
item->setText( 0, title );
item->setType( MediaItem::PLAYLISTITEM );
item->m_playlist = pl;
item->m_track = track;
item->bundleFromTrack( track, realPath(track->ipod_path) );
item->m_order = i;
cur = cur->next;
i++;
}
}
TQString
IpodMediaDevice::itunesDir(const TQString &p) const
{
TQString base( ":iPod_Control" );
if( m_isMobile )
base = ":iTunes:iTunes_Control";
else if( m_isIPhone )
base = ":iTunes_Control";
if( !p.startsWith( ":" ) )
base += ':';
return base + p;
}
TQString
IpodMediaDevice::realPath(const char *ipodPath)
{
TQString path;
if(m_itdb)
{
path = TQFile::decodeName(itdb_get_mountpoint(m_itdb));
path.append(TQString(ipodPath).replace(':', "/"));
}
return path;
}
TQString
IpodMediaDevice::ipodPath(const TQString &realPath)
{
if(m_itdb)
{
TQString mp = TQFile::decodeName(itdb_get_mountpoint(m_itdb));
if(realPath.startsWith(mp))
{
TQString path = realPath;
path = path.mid(mp.length());
path = path.replace('/', ":");
return path;
}
}
return TQString();
}
class IpodWriteDBJob : public ThreadManager::DependentJob
{
public:
IpodWriteDBJob( TQObject *parent, Itdb_iTunesDB *itdb, bool isShuffle, bool *resultPtr )
: ThreadManager::DependentJob( parent, "IpodWriteDBJob" )
, m_itdb( itdb )
, m_isShuffle( isShuffle )
, m_resultPtr( resultPtr )
, m_return( true )
{}
private:
virtual bool doJob()
{
if( !m_itdb )
{
m_return = false;
}
GError *error = 0;
if (m_return && !itdb_write (m_itdb, &error))
{ /* an error occurred */
m_return = false;
if(error)
{
if (error->message)
debug() << "itdb_write error: " << error->message << endl;
else
debug() << "itdb_write error: " << "error->message == 0!" << endl;
g_error_free (error);
}
error = 0;
}
if( m_return && m_isShuffle )
{
/* write shuffle data */
if (!itdb_shuffle_write (m_itdb, &error))
{ /* an error occurred */
m_return = false;
if(error)
{
if (error->message)
debug() << "itdb_shuffle_write error: " << error->message << endl;
else
debug() << "itdb_shuffle_write error: " << "error->message == 0!" << endl;
g_error_free (error);
}
error = 0;
}
}
return true;
}
virtual void completeJob()
{
*m_resultPtr = m_return;
}
Itdb_iTunesDB *m_itdb;
bool m_isShuffle;
bool *m_resultPtr;
bool m_return;
};
bool
IpodMediaDevice::writeITunesDB( bool threaded )
{
if(!m_itdb)
return false;
if(m_dbChanged)
{
bool ok = false;
if( !threaded || MediaBrowser::instance()->isQuitting() )
{
if( !m_itdb )
{
return false;
}
ok = true;
GError *error = 0;
if ( !itdb_write (m_itdb, &error) )
{ /* an error occurred */
if(error)
{
if (error->message)
debug() << "itdb_write error: " << error->message << endl;
else
debug() << "itdb_write error: " << "error->message == 0!" << endl;
g_error_free (error);
}
error = 0;
ok = false;
}
if( m_isShuffle )
{
/* write shuffle data */
if (!itdb_shuffle_write (m_itdb, &error))
{ /* an error occurred */
if(error)
{
if (error->message)
debug() << "itdb_shuffle_write error: " << error->message << endl;
else
debug() << "itdb_shuffle_write error: " << "error->message == 0!" << endl;
g_error_free (error);
}
error = 0;
ok = false;
}
}
}
else
{
ThreadManager::instance()->queueJob( new IpodWriteDBJob( this, m_itdb, m_isShuffle, &ok ) );
while( ThreadManager::instance()->isJobPending( "IpodWriteDBJob" ) )
{
kapp->processEvents();
usleep( 10000 );
}
}
if( ok )
{
m_dbChanged = false;
}
else
{
Amarok::StatusBar::instance()->longMessage(
i18n("Media device: failed to write iPod database"),
KDE::StatusBar::Error );
}
return ok;
}
return true;
}
IpodMediaItem *
IpodMediaDevice::getArtist(const TQString &artist)
{
for(IpodMediaItem *it = dynamic_cast<IpodMediaItem *>(m_view->firstChild());
it;
it = dynamic_cast<IpodMediaItem *>(it->nextSibling()))
{
if(it->m_type==MediaItem::ARTIST && artist == it->text(0))
return it;
}
return 0;
}
IpodMediaItem *
IpodMediaDevice::getAlbum(const TQString &artist, const TQString &album)
{
IpodMediaItem *item = getArtist(artist);
if(item)
return dynamic_cast<IpodMediaItem *>(item->findItem(album));
return 0;
}
IpodMediaItem *
IpodMediaDevice::getTrack(const TQString &artist, const TQString &album, const TQString &title,
int discNumber, int trackNumber, const PodcastEpisodeBundle *peb)
{
IpodMediaItem *item = getAlbum(artist, album);
if(item)
{
for( IpodMediaItem *track = dynamic_cast<IpodMediaItem *>(item->findItem(title));
track;
track = dynamic_cast<IpodMediaItem *>(item->findItem(title, track)) )
{
if( ( discNumber==-1 || track->bundle()->discNumber()==discNumber )
&& ( trackNumber==-1 || track->bundle()->track()==trackNumber ) )
return track;
}
}
item = getAlbum( i18n( "Various Artists" ), album );
if( item )
{
TQString t = artist + i18n(" - ") + title;
for( IpodMediaItem *track = dynamic_cast<IpodMediaItem *>(item->findItem(t));
track;
track = dynamic_cast<IpodMediaItem *>(item->findItem(t, track)) )
{
if( ( discNumber==-1 || track->bundle()->discNumber()==discNumber )
&& ( trackNumber==-1 || track->bundle()->track()==trackNumber ) )
return track;
}
}
if(m_podcastItem)
{
item = dynamic_cast<IpodMediaItem *>(m_podcastItem->findItem(album));
if(item)
{
for( IpodMediaItem *track = dynamic_cast<IpodMediaItem *>(item->findItem(title));
track;
track = dynamic_cast<IpodMediaItem *>(item->findItem(title, track)) )
{
if( ( discNumber==-1 || track->bundle()->discNumber()==discNumber )
&& ( trackNumber==-1 || track->bundle()->track()==trackNumber )
&& ( !track->bundle()->podcastBundle() || !peb
|| track->bundle()->podcastBundle()->url() == peb->url() ) )
return track;
}
}
}
return 0;
}
IpodMediaItem *
IpodMediaDevice::getTrack( const Itdb_Track *itrack )
{
TQString artist = TQString::fromUtf8( itrack->artist );
TQString album = TQString::fromUtf8( itrack->album );
TQString title = TQString::fromUtf8( itrack->title );
IpodMediaItem *item = getAlbum( artist, album );
if(item)
{
for( IpodMediaItem *track = dynamic_cast<IpodMediaItem *>(item->findItem( title ) );
track;
track = dynamic_cast<IpodMediaItem *>(item->findItem(title, track)) )
{
if( track->m_track == itrack )
return track;
}
}
item = getAlbum( i18n( "Various Artists" ), album );
if( item )
{
TQString t = artist + i18n(" - ") + title;
for( IpodMediaItem *track = dynamic_cast<IpodMediaItem *>(item->findItem(t));
track;
track = dynamic_cast<IpodMediaItem *>(item->findItem(t, track)) )
{
if( track->m_track == itrack )
return track;
}
}
if(m_podcastItem)
{
item = dynamic_cast<IpodMediaItem *>(m_podcastItem->findItem(album));
if(item)
{
for( IpodMediaItem *track = dynamic_cast<IpodMediaItem *>(item->findItem(title));
track;
track = dynamic_cast<IpodMediaItem *>(item->findItem(title, track)) )
{
if( track->m_track == itrack )
return track;
}
}
}
return 0;
}
KURL
IpodMediaDevice::determineURLOnDevice(const MetaBundle &bundle)
{
if( !m_itdb )
{
debug() << "m_itdb is NULL" << endl;
return KURL();
}
TQString local = bundle.filename();
TQString type = TQString(local.section('.', -1)).lower();
TQString trackpath;
TQString realpath;
do
{
int num = std::rand() % 1000000;
int music_dirs = itdb_musicdirs_number(m_itdb) > 1 ? itdb_musicdirs_number(m_itdb) : 20;
int dir = num % music_dirs;
TQString dirname;
dirname.sprintf( "%s:Music:f%02d", itunesDir().latin1(), dir );
if( !pathExists( dirname ) )
{
TQString realdir = realPath(dirname.latin1());
TQDir qdir( realdir );
qdir.mkdir( realdir );
}
TQString filename;
filename.sprintf( ":kpod%07d.%s", num, type.latin1() );
trackpath = dirname + filename;
}
while( pathExists( trackpath, &realpath ) );
return realpath;
}
bool
IpodMediaDevice::removeDBTrack(Itdb_Track *track)
{
if(!m_itdb)
return false;
if(!track)
return false;
if(track->itdb != m_itdb)
{
return false;
}
m_dbChanged = true;
Itdb_Playlist *mpl = itdb_playlist_mpl(m_itdb);
while(itdb_playlist_contains_track(mpl, track))
{
itdb_playlist_remove_track(mpl, track);
}
GList *cur = m_itdb->playlists;
while(cur)
{
Itdb_Playlist *pl = (Itdb_Playlist *)cur->data;
while(itdb_playlist_contains_track(pl, track))
{
itdb_playlist_remove_track(pl, track);
}
cur = cur->next;
}
// also frees track's memory
itdb_track_remove(track);
return true;
}
bool
IpodMediaDevice::getCapacity( TDEIO::filesize_t *total, TDEIO::filesize_t *available )
{
if(!m_itdb)
return false;
#ifdef HAVE_STATVFS
TQString path;
if ( !pathExists( itunesDir(), &path ) )
return false;
struct statvfs buf;
if(statvfs(TQFile::encodeName(path), &buf) != 0)
{
*total = 0;
*available = 0;
return false;
}
*total = buf.f_blocks * (TDEIO::filesize_t)buf.f_frsize;
*available = buf.f_bavail * (TDEIO::filesize_t)buf.f_frsize;
return *total > 0;
#else
return false;
#endif
}
void
IpodMediaDevice::rmbPressed( TQListViewItem* qitem, const TQPoint& point, int )
{
MediaItem *item = dynamic_cast<MediaItem *>(qitem);
bool locked = m_mutex.locked();
KURL::List urls = m_view->nodeBuildDragList( 0 );
TDEPopupMenu menu( m_view );
enum Actions { CREATE_PLAYLIST, APPEND, LOAD, QUEUE,
COPY_TO_COLLECTION,
BURN_ARTIST, BURN_ALBUM, BURN_DATACD, BURN_AUDIOCD,
RENAME, SUBSCRIBE,
MAKE_PLAYLIST, ADD_TO_PLAYLIST, ADD,
DELETE_PLAYED, DELETE_FROM_IPOD, REMOVE_FROM_PLAYLIST,
FIRST_PLAYLIST};
TDEPopupMenu *playlistsMenu = 0;
if ( item )
{
if( item->type() == MediaItem::PLAYLISTSROOT )
{
menu.insertItem( SmallIconSet(Amarok::icon( "add_playlist" )),
i18n("Create Playlist..."), CREATE_PLAYLIST );
}
else
{
menu.insertItem( SmallIconSet( Amarok::icon( "playlist" ) ), i18n( "&Load" ), LOAD );
menu.insertItem( SmallIconSet( Amarok::icon( "add_playlist" ) ), i18n( "&Append to Playlist" ), APPEND );
menu.insertItem( SmallIconSet( Amarok::icon( "fastforward" ) ), i18n( "&Queue Tracks" ), QUEUE );
}
menu.insertSeparator();
menu.insertItem( SmallIconSet( Amarok::icon( "collection" ) ), i18n( "&Copy Files to Collection..." ), COPY_TO_COLLECTION );
switch( item->type() )
{
case MediaItem::ARTIST:
menu.insertItem( SmallIconSet( Amarok::icon( "media-optical-cdrom" ) ), i18n( "Burn All Tracks by This Artist" ), BURN_ARTIST );
menu.setItemEnabled( BURN_ARTIST, K3bExporter::isAvailable() );
break;
case MediaItem::ALBUM:
menu.insertItem( SmallIconSet( Amarok::icon( "media-optical-cdrom" ) ), i18n( "Burn This Album" ), BURN_ALBUM );
menu.setItemEnabled( BURN_ALBUM, K3bExporter::isAvailable() );
break;
default:
menu.insertItem( SmallIconSet( Amarok::icon( "media-optical-cdrom" ) ), i18n( "Burn to CD as Data" ), BURN_DATACD );
menu.setItemEnabled( BURN_DATACD, K3bExporter::isAvailable() );
menu.insertItem( SmallIconSet( Amarok::icon( "media-optical-cdaudio" ) ), i18n( "Burn to CD as Audio" ), BURN_AUDIOCD );
menu.setItemEnabled( BURN_AUDIOCD, K3bExporter::isAvailable() );
break;
}
menu.insertSeparator();
if( (item->type() == MediaItem::PODCASTITEM
|| item->type() == MediaItem::PODCASTCHANNEL) )
{
IpodMediaItem *it = static_cast<IpodMediaItem *>(item);
menu.insertItem( SmallIconSet( Amarok::icon( "podcast" ) ), i18n( "Subscribe to This Podcast" ), SUBSCRIBE );
//menu.setItemEnabled( SUBSCRIBE, item->bundle()->podcastBundle() && item->bundle()->podcastBundle()->parent().isValid() );
menu.setItemEnabled( SUBSCRIBE, it->m_podcastInfo && !it->m_podcastInfo->rss.isEmpty() );
menu.insertSeparator();
}
switch( item->type() )
{
case MediaItem::ARTIST:
case MediaItem::ALBUM:
case MediaItem::TRACK:
case MediaItem::PODCASTCHANNEL:
case MediaItem::PODCASTSROOT:
case MediaItem::PODCASTITEM:
if(m_playlistItem)
{
menu.insertItem( SmallIconSet( Amarok::icon( "playlist" ) ), i18n( "Make Media Device Playlist" ), MAKE_PLAYLIST );
menu.setItemEnabled( MAKE_PLAYLIST, !locked );
playlistsMenu = new TDEPopupMenu(&menu);
int i=0;
for(MediaItem *it = dynamic_cast<MediaItem *>(m_playlistItem->firstChild());
it;
it = dynamic_cast<MediaItem *>(it->nextSibling()))
{
playlistsMenu->insertItem( SmallIconSet( Amarok::icon( "playlist" ) ), it->text(0), FIRST_PLAYLIST+i );
i++;
}
menu.insertItem( SmallIconSet( Amarok::icon( "playlist" ) ), i18n("Add to Playlist"), playlistsMenu, ADD_TO_PLAYLIST );
menu.setItemEnabled( ADD_TO_PLAYLIST, !locked && m_playlistItem->childCount()>0 );
menu.insertSeparator();
}
if( item->type() == MediaItem::ARTIST ||
item->type() == MediaItem::ALBUM ||
item->type() == MediaItem::TRACK ||
item->type() == MediaItem::ORPHANED )
{
menu.insertItem( SmallIconSet( Amarok::icon( "edit" ) ),
i18n( "Edit &Information...", "Edit &Information for %n Tracks...", urls.count()),
RENAME );
}
break;
case MediaItem::ORPHANED:
case MediaItem::ORPHANEDROOT:
menu.insertItem( SmallIconSet( Amarok::icon( "edit" ) ), i18n( "Add to Database" ), ADD );
menu.setItemEnabled( ADD, !locked );
break;