/***************************************************************************
    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(TQt::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, TQ_SIGNAL(activated(int)), TQ_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, TQ_SIGNAL(activated(int)), TQ_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-unmounted" ) ), 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-unmounted" ) ), i18n( "Burn This Album" ), BURN_ALBUM );
            menu.setItemEnabled( BURN_ALBUM, K3bExporter::isAvailable() );
            break;

        default:
            menu.insertItem( SmallIconSet( Amarok::icon( "media-optical-cdrom-unmounted" ) ), i18n( "Burn to CD as Data" ), BURN_DATACD );
            menu.setItemEnabled( BURN_DATACD, K3bExporter::isAvailable() );
            menu.insertItem( SmallIconSet( Amarok::icon( "media-optical-cdaudio-unmounted" ) ), 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;

        case MediaItem::PLAYLIST:
            menu.insertItem( SmallIconSet( Amarok::icon( "edit" ) ), i18n( "Rename" ), RENAME );
            menu.setItemEnabled( RENAME, !locked );
            break;

        default:
            break;
        }

        if( item->type() == MediaItem::PLAYLIST || item->type() == MediaItem::PLAYLISTITEM )
        {
            menu.insertItem( SmallIconSet( Amarok::icon( "remove_from_playlist" ) ),
                    item->type()==MediaItem::PLAYLIST ? i18n( "Remove Playlist" ) : i18n( "Remove from Playlist" ),
                    REMOVE_FROM_PLAYLIST );
            menu.setItemEnabled( REMOVE_FROM_PLAYLIST, !locked );
        }
        if( item->type() == MediaItem::PODCASTSROOT || item->type() == MediaItem::PODCASTCHANNEL )
        {
            menu.insertItem( SmallIconSet( Amarok::icon( "remove" ) ), i18n( "Delete Podcasts Already Played" ), DELETE_PLAYED );
            menu.setItemEnabled( DELETE_PLAYED, !locked );
        }
        menu.insertItem( SmallIconSet( Amarok::icon( "remove" ) ),
                i18n( "Delete Track from iPod", "Delete %n Tracks from iPod", urls.count() ),
                DELETE_FROM_IPOD );
        menu.setItemEnabled( DELETE_FROM_IPOD, !locked && urls.count() > 0 );
    }

    int id =  menu.exec( point );
    switch( id )
    {
        case CREATE_PLAYLIST:
            break;
        case LOAD:
            Playlist::instance()->insertMedia( urls, Playlist::Replace );
            break;
        case APPEND:
            Playlist::instance()->insertMedia( urls, Playlist::Append );
            break;
        case QUEUE:
            Playlist::instance()->insertMedia( urls, Playlist::Queue );
            break;
        case COPY_TO_COLLECTION:
            {
                TQPtrList<MediaItem> items;
                m_view->getSelectedLeaves( 0, &items );

                KURL::List urls;
                for( MediaItem *it = items.first();
                        it;
                        it = items.next() )
                {
                    if( it->url().isValid() )
                        urls << it->url();
                }

                CollectionView::instance()->organizeFiles( urls, i18n("Copy Files To Collection"), true );
            }
            break;
        case BURN_ARTIST:
            K3bExporter::instance()->exportArtist( item->text(0) );
            break;
        case BURN_ALBUM:
            K3bExporter::instance()->exportAlbum( item->text(0) );
            break;
        case BURN_DATACD:
            K3bExporter::instance()->exportTracks( urls, K3bExporter::DataCD );
            break;
        case BURN_AUDIOCD:
            K3bExporter::instance()->exportTracks( urls, K3bExporter::AudioCD );
            break;
        case SUBSCRIBE:
            PlaylistBrowser::instance()->addPodcast( static_cast<IpodMediaItem *>(item)->m_podcastInfo->rss );
            break;
        case RENAME:
            if( item->type() == MediaItem::PLAYLIST )
            {
                m_view->rename(item, 0);
            }
            else
            {
                TagDialog *dialog = NULL;
                if( urls.count() == 1 )
                    dialog = new TagDialog( urls.first(), m_view );
                else
                    dialog = new TagDialog( urls, m_view );
                dialog->show();
            }
            break;
        default:
            break;
    }

    if( !m_mutex.locked() )
    {
        switch( id )
        {
            case CREATE_PLAYLIST:
            case MAKE_PLAYLIST:
                {
                    TQPtrList<MediaItem> items;
                    if( id == MAKE_PLAYLIST )
                        m_view->getSelectedLeaves( 0, &items );
                    TQString base(i18n("New Playlist"));
                    TQString name = base;
                    int i=1;
                    while(m_playlistItem->findItem(name))
                    {
                        TQString num;
                        num.setNum(i);
                        name = base + ' ' + num;
                        i++;
                    }
                    MediaItem *pl = newPlaylist(name, m_playlistItem, items);
                    m_view->ensureItemVisible(pl);
                    m_view->rename(pl, 0);
                }
                break;
            case ADD:
                {
                    int dupes = 0;

                    if(item->type() == MediaItem::ORPHANEDROOT)
                    {
                        MediaItem *next = 0;
                        for(MediaItem *it = dynamic_cast<MediaItem *>(item->firstChild());
                                it;
                                it = next)
                        {
                            next = dynamic_cast<MediaItem *>(it->nextSibling());
                            if( trackExists( *it->bundle() ) )
                            {
                                dupes++;
                                continue;
                            }

                            item->takeItem(it);
                            insertTrackIntoDB(it->url().path(), *it->bundle(), *it->bundle(), 0);
                            delete it;
                        }
                    }
                    else
                    {
                        TQPtrList<MediaItem> items;
                        m_view->getSelectedLeaves( 0, &items );
                        for( TQPtrList<MediaItem>::iterator it = items.begin();
                                it != items.end();
                                it++ )
                        {
                            IpodMediaItem *i = dynamic_cast<IpodMediaItem *>( *it );
                            if( !i || i->type() != MediaItem::ORPHANED )
                                continue;

                            if( trackExists( *i->bundle() ) )
                            {
                                dupes++;
                                continue;
                            }

                            i->parent()->takeItem(i);
                            insertTrackIntoDB(i->url().path(), *i->bundle(), *i->bundle(), 0);
                            delete i;
                        }
                    }

                    if( dupes > 0 )
                        Amarok::StatusBar::instance()->shortMessage( i18n(
                                    "One duplicate track not added to database",
                                    "%n duplicate tracks not added to database", dupes ) );
                }
                break;
            case DELETE_PLAYED:
                {
                    MediaItem *podcasts = 0;
                    if(item->type() == MediaItem::PODCASTCHANNEL)
                        podcasts = dynamic_cast<MediaItem *>(item->parent());
                    else
                        podcasts = item;
                    deleteFromDevice( podcasts, true );
                }
                break;
            case REMOVE_FROM_PLAYLIST:
                deleteFromDevice(m_playlistItem, None);
                break;
            case DELETE_FROM_IPOD:
                deleteFromDevice();
                break;
           default:
                if( playlistsMenu && id >= FIRST_PLAYLIST )
                {
                    TQString name = playlistsMenu->text(id);
                    if( name != TQString() )
                    {
                        MediaItem *list = m_playlistItem->findItem(name);
                        if(list)
                        {
                            MediaItem *after = 0;
                            for(MediaItem *it = dynamic_cast<MediaItem *>(list->firstChild());
                                    it;
                                    it = dynamic_cast<MediaItem *>(it->nextSibling()))
                                after = it;
                            TQPtrList<MediaItem> items;
                            m_view->getSelectedLeaves( 0, &items );
                            addToPlaylist( list, after, items );
                        }
                    }
                }
                break;
        }

        if( m_dbChanged && lockDevice( true ) )
        {
            synchronizeDevice();
            unlockDevice();
        }
    }
}

TQStringList
IpodMediaDevice::supportedFiletypes()
{
    TQStringList list;
    list << "mp3";
    list << "m4a";
    list << "m4b";
    list << "wav";
    list << "mp4";
    list << "aa";

    if( m_supportsVideo )
    {
        list << "m4v";
        list << "mp4v";
        list << "mov";
        list << "mpg";
    }

    if( m_rockboxFirmware )
    {
        list << "ogg";
        list << "mpc";
        list << "ac3";
        list << "adx";
        list << "aiff";
        list << "flac";
        list << "mid";
        list << "midi";
        list << "shn";
        list << "wv";
        list << "ape";
        list << "tta";
    }

    return list;
}

void
IpodMediaDevice::addConfigElements( TQWidget *parent )
{
    m_autoDeletePodcastsCheck = new TQCheckBox( parent );
    m_autoDeletePodcastsCheck->setText( i18n( "&Automatically delete podcasts" ) );
    TQToolTip::add( m_autoDeletePodcastsCheck, i18n( "Automatically delete podcast shows already played when connecting device" ) );
    m_autoDeletePodcastsCheck->setChecked( m_autoDeletePodcasts );

    m_syncStatsCheck = new TQCheckBox( parent );
    m_syncStatsCheck->setText( i18n( "&Synchronize with Amarok statistics" ) );
    TQToolTip::add( m_syncStatsCheck, i18n( "Synchronize with Amarok statistics and submit tracks played to last.fm" ) );
    m_syncStatsCheck->setChecked( m_syncStats );
}

void
IpodMediaDevice::removeConfigElements( TQWidget * /*parent*/ )
{
    delete m_syncStatsCheck;
    m_syncStatsCheck = 0;

    delete m_autoDeletePodcastsCheck;
    m_autoDeletePodcastsCheck = 0;

}

void
IpodMediaDevice::applyConfig()
{
    m_autoDeletePodcasts = m_autoDeletePodcastsCheck->isChecked();
    m_syncStats = m_syncStatsCheck->isChecked();

    setConfigBool( "SyncStats", m_syncStats );
    setConfigBool( "AutoDeletePodcasts", m_autoDeletePodcasts );
}

void
IpodMediaDevice::loadConfig()
{
    MediaDevice::loadConfig();

    m_syncStats = configBool( "SyncStats", false );
    m_autoDeletePodcasts = configBool( "AutoDeletePodcasts", false );
    m_autoConnect = configBool( "AutoConnect", true );
}

bool
IpodMediaDevice::pathExists( const TQString &ipodPath, TQString *realPath )
{
    TQDir curDir( mountPoint() );
    curDir.setFilter(curDir.filter() | TQDir::Hidden);
    TQString curPath = mountPoint();
    TQStringList components = TQStringList::split( ":", ipodPath );

    bool found = false;
    TQStringList::iterator it = components.begin();
    for( ; it != components.end(); ++it )
    {
        found = false;
        for(uint i=0; i<curDir.count(); i++)
        {
            if( curDir[i].lower() == (*it).lower())
            {
                curPath += '/' + curDir[i];
                curDir.cd( curPath );
                found = true;
                break;
            }
        }
        if(!found)
            break;
    }

    for( ; it != components.end(); ++it )
        curPath += '/' + *it;

    //debug() << ipodPath << ( found ? "" : " not" ) << " found, actually " << curPath << endl;

    if( realPath )
        *realPath = curPath;

    return found;
}

void
IpodMediaDevice::fileDeleted( TDEIO::Job *job )  //SLOT
{
    if(job->error())
    {
        debug() << "file deletion failed: " << job->errorText() << endl;
    }
    m_waitForDeletion = false;
    m_parent->updateStats();
}

void
IpodMediaDevice::deleteFile( const KURL &url )
{
    debug() << "deleting " << url.prettyURL() << endl;
    m_waitForDeletion = true;
    TDEIO::Job *job = TDEIO::file_delete( url, false );
    connect( job, TQ_SIGNAL( result( TDEIO::Job * ) ),
            this,  TQ_SLOT( fileDeleted( TDEIO::Job * ) ) );
    do
    {
        kapp->processEvents( 100 );
        if( isCanceled() )
            break;
        usleep( 10000 );
    } while( m_waitForDeletion );

    if(!isTransferring())
        setProgress( progress() + 1 );
}

#include "ipodmediadevice.moc"
