1
/******************************************************************************
2
 * Copyright (C) 2004 Christian Muehlhaeuser <chris@chris.de>                 *
3
 *           (C) 2005-2006 Martin Aumueller <aumuell@reserv.at>               *
4
 *           (C) 2005 Seb Ruiz <ruiz@kde.org>                                 *
5
 *           (C) 2006 T.R.Shashwath <trshash84@gmail.com>                     *
6
 *           (c) 2007 Jeff Mitchell <kde-dev@emailgoeshere.com>               *
7
 *                                                                            *
8
 * This program is free software; you can redistribute it and/or              *
9
 * modify it under the terms of the GNU General Public License as             *
10
 * published by the Free Software Foundation; either version 2 of             *
11
 * the License, or (at your option) any later version.                        *
12
 *                                                                            *
13
 * This program is distributed in the hope that it will be useful,            *
14
 * but WITHOUT ANY WARRANTY; without even the implied warranty of             *
15
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the              *
16
 * GNU General Public License for more details.                               *
17
 *                                                                            *
18
 * You should have received a copy of the GNU General Public License          *
19
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.      *
20
 ******************************************************************************/
21
22
23
#define DEBUG_PREFIX "MediaDevice"
24
25
#include "MediaDevice.h"
26
27
#include "Amarok.h"
28
#include "amarokconfig.h"
29
#include "App.h"
30
#include "Debug.h"
31
#include "mediabrowser.h"
32
#include "MediaItem.h"
33
#include "meta/file/File.h"
34
#include "meta/Playlist.h"
35
#include "meta/PlaylistFileSupport.h"
36
#include "PluginManager.h"
37
#include "AmarokProcess.h"
38
#include "ScriptManager.h"
39
#include "statusbar/StatusBar.h"
40
41
#include <KIO/Job>
42
#include <KMessageBox>
43
44
#include <QTimer>
45
46
#include <unistd.h>
47
48
MediaDevice::MediaDevice()
49
    : Amarok::Plugin()
50
    , m_name( QString() )
51
    , m_hasMountPoint( true )
52
    , m_autoDeletePodcasts( false )
53
    , m_syncStats( false )
54
    , m_transcode( false )
55
    , m_transcodeAlways( false )
56
    , m_transcodeRemove( false )
57
    , sysProc ( 0 )
58
    , m_parent( 0 )
59
    , m_view( 0 )
60
    , m_udi( QString() )
61
    , m_deviceNode( QString() )
62
    , m_mountPoint( QString() )
63
    , m_fsType( QString() )
64
    , m_wait( false )
65
    , m_requireMount( false )
66
    , m_canceled( false )
67
    , m_transferring( false )
68
    , m_deleting( false )
69
    , m_deferredDisconnect( false )
70
    , m_scheduledDisconnect( false )
71
    , m_transfer( true )
72
    , m_configure( true )
73
    , m_customButton( false )
74
    , m_playlistItem( 0 )
75
    , m_podcastItem( 0 )
76
    , m_invisibleItem( 0 )
77
    , m_staleItem( 0 )
78
    , m_orphanedItem( 0 )
79
{
80
    sysProc = new AmarokShellProcess(); Q_CHECK_PTR(sysProc);
81
}
82
83
void MediaDevice::init( MediaBrowser* parent )
84
{
85
    m_parent = parent;
86
    if( !m_view )
87
       m_view = new MediaView( m_parent->m_views, this );
88
    m_view->hide();
89
}
90
91
MediaDevice::~MediaDevice()
92
{
93
    delete m_view;
94
    delete sysProc;
95
}
96
97
bool
98
MediaDevice::isSpecialItem( MediaItem *item )
99
{
100
    return (item == m_playlistItem) ||
101
        (item == m_podcastItem) ||
102
        (item == m_invisibleItem) ||
103
        (item == m_staleItem) ||
104
        (item == m_orphanedItem);
105
}
106
107
void
108
MediaDevice::loadConfig()
109
{
110
    m_transcode = configBool( "Transcode" );
111
    m_transcodeAlways = configBool( "TranscodeAlways" );
112
    m_transcodeRemove = configBool( "TranscodeRemove" );
113
    m_preconnectcmd = configString( "PreConnectCommand" );
114
    if( m_preconnectcmd.isEmpty() )
115
        m_preconnectcmd = configString( "MountCommand" );
116
    m_postdisconnectcmd = configString( "PostDisconnectCommand" );
117
    if( m_postdisconnectcmd.isEmpty() )
118
        m_postdisconnectcmd = configString( "UmountCommand" );
119
    if( m_requireMount && m_postdisconnectcmd.isEmpty() )
120
        m_postdisconnectcmd = "kdeeject -q %d";
121
}
122
123
QString
124
MediaDevice::configString( const QString &name, const QString &defValue )
125
{
126
    QString configName = "MediaDevice";
127
    if( !udi().isEmpty() )
128
        configName += '_' + udi();
129
    KConfigGroup config = Amarok::config( configName );
130
    return config.readEntry( name, defValue );
131
}
132
133
void
134
MediaDevice::setConfigString( const QString &name, const QString &value )
135
{
136
    QString configName = "MediaDevice";
137
    if( !udi().isEmpty() )
138
        configName += '_' + udi();
139
    KConfigGroup config = Amarok::config( configName );
140
    config.writeEntry( name, value );
141
}
142
143
bool
144
MediaDevice::configBool( const QString &name, bool defValue )
145
{
146
    QString configName = "MediaDevice";
147
    if( !udi().isEmpty() )
148
        configName += '_' + udi();
149
    KConfigGroup config = Amarok::config( configName );
150
    return config.readEntry( name, defValue );
151
}
152
153
void
154
MediaDevice::setConfigBool( const QString &name, bool value )
155
{
156
    QString configName = "MediaDevice";
157
    if( !udi().isEmpty() )
158
        configName += '_' + udi();
159
    KConfigGroup config = Amarok::config( configName );
160
    config.writeEntry( name, value );
161
}
162
163
MediaView *
164
MediaDevice::view()
165
{
166
    return m_view;
167
}
168
169
void
170
MediaDevice::hideProgress()
171
{
172
    m_parent->m_progressBox->hide();
173
}
174
175
void
176
MediaDevice::updateRootItems()
177
{
178
    if(m_podcastItem)
179
        m_podcastItem->setVisible(m_podcastItem->childCount() > 0);
180
    if(m_invisibleItem)
181
        m_invisibleItem->setVisible(m_invisibleItem->childCount() > 0);
182
    if(m_staleItem)
183
        m_staleItem->setVisible(m_staleItem->childCount() > 0);
184
    if(m_orphanedItem)
185
        m_orphanedItem->setVisible(m_orphanedItem->childCount() > 0);
186
}
187
188
Meta::TrackList
189
MediaDevice::tracksToSync( const QString &name, const KUrl &url )
190
{
191
    Meta::TrackList tracks = Meta::loadPlaylist( url )->tracks();
192
193
    preparePlaylistForSync( name, tracks );
194
195
    return tracks;
196
}
197
198
//TODO port to meta & new collection interface
199
//BundleList
200
//MediaDevice::bundlesToSync( const QString &name, const QString &query )
201
//{
202
//    const QStringList values = CollectionDB::instance()->query( query );
203
//
204
//    BundleList bundles;
205
//    for( QStringList::const_iterator it = values.begin(); it != values.end(); ++it )
206
//        bundles += CollectionDB::instance()->bundleFromQuery( &it );
207
//    preparePlaylistForSync( name, bundles );
208
//    return bundles;
209
//}
210
211
void
212
MediaDevice::preparePlaylistForSync( const QString &name, const Meta::TrackList &tracks )
213
{
214
    if( ! m_playlistItem ) // might be syncing a new playlist from the playlist browser
215
        return;
216
    MediaItem *pl = m_playlistItem->findItem( name );
217
    if( pl )
218
    {
219
        MediaItem *next = 0;
220
        for( MediaItem *it = static_cast<MediaItem *>(pl->firstChild());
221
                it;
222
                it = next )
223
        {
224
            next = static_cast<MediaItem *>(it->nextSibling());
225
            const Meta::TrackPtr track = Meta::TrackPtr::dynamicCast( (*it).meta() );
226
            if( !track )
227
                continue;
228
            if( isOnOtherPlaylist( name, track ) )
229
                continue;
230
            if( isInTrackList( tracks, track ) )
231
                continue;
232
            deleteItemFromDevice( it );
233
        }
234
        deleteItemFromDevice( pl, None );
235
    }
236
    purgeEmptyItems();
237
}
238
239
bool
240
MediaDevice::trackMatch( Meta::TrackPtr track1, Meta::TrackPtr track2 )
241
{
242
    if (!track1 || !track2)
243
        return false;
244
245
    if (track1 == track2)
246
        return true;
247
248
    if ( track1->name() != track2->name() )
249
        return false;
250
251
    if ( track1->artist()->name() != track2->artist()->name() )
252
        return false;
253
254
    return true;
255
}
256
257
bool
258
MediaDevice::isInTrackList( const Meta::TrackList &list, const Meta::TrackPtr track )
259
{
260
    for( int i = 0, size = list.count(); i < size; i++ )
261
    {
262
        if( trackMatch( list[i], track ) )
263
            return true;
264
    }
265
266
    return false;
267
}
268
269
bool
270
MediaDevice::isOnOtherPlaylist( const QString &playlistToAvoid, const Meta::TrackPtr track )
271
{
272
    if (!track)
273
        return false;
274
275
    for( MediaItem *it = static_cast<MediaItem *>(m_playlistItem->firstChild());
276
            it;
277
            it = static_cast<MediaItem *>(it->nextSibling()) )
278
    {
279
        if( it->text( 0 )  == playlistToAvoid )
280
            continue;
281
        if( isOnPlaylist( *it, track ) )
282
            return true;
283
    }
284
285
    return false;
286
}
287
288
289
bool
290
MediaDevice::isOnPlaylist( const MediaItem &playlist, const Meta::TrackPtr track )
291
{
292
    for( MediaItem *it = static_cast<MediaItem *>(playlist.firstChild());
293
            it;
294
            it = static_cast<MediaItem *>(it->nextSibling()) )
295
    {
296
        Meta::TrackPtr playlist_track = Meta::TrackPtr::dynamicCast(it->meta());
297
        if( !playlist_track )
298
            continue;
299
        if( trackMatch( playlist_track, track ) )
300
            return true;
301
    }
302
303
    return false;
304
}
305
306
307
void
308
MediaDevice::copyTrackFromDevice( MediaItem *item )
309
{
310
    debug() << "copyTrackFromDevice: not copying " << item->url() << ": not implemented";
311
}
312
313
QString
314
MediaDevice::replaceVariables( const QString &cmd )
315
{
316
    QString result = cmd;
317
    result.replace( "%d", deviceNode() );
318
    result.replace( "%m", mountPoint() );
319
    return result;
320
}
321
322
int MediaDevice::runPreConnectCommand()
323
{
324
    if( m_preconnectcmd.isEmpty() )
325
        return 0;
326
327
    QString cmd = replaceVariables( m_preconnectcmd );
328
329
    debug() << "running pre-connect command: [" << cmd << "]";
330
    int e=sysCall(cmd);
331
    debug() << "pre-connect: e=" << e;
332
    return e;
333
}
334
335
int MediaDevice::runPostDisconnectCommand()
336
{
337
    if( m_postdisconnectcmd.isEmpty() )
338
        return 0;
339
340
    QString cmd = replaceVariables( m_postdisconnectcmd );
341
    debug() << "running post-disconnect command: [" << cmd << "]";
342
    int e=sysCall(cmd);
343
    debug() << "post-disconnect: e=" << e;
344
345
    return e;
346
}
347
348
int MediaDevice::sysCall( const QString &command )
349
{
350
    if( sysProc->state() != AmarokShellProcess::NotRunning )  return -1;
351
352
    sysProc->clearProgram();
353
    (*sysProc) << command;
354
    sysProc->setOutputChannelMode( AmarokShellProcess::MergedChannels );
355
    if( sysProc->execute( ) != 0 )
356
        kFatal() << i18n("could not execute %1", command.toLocal8Bit().data());
357
358
    return (sysProc->exitStatus());
359
}
360
361
void
362
MediaDevice::abortTransfer()
363
{
364
    setCanceled( true );
365
    cancelTransfer();
366
}
367
368
bool
369
MediaDevice::kioCopyTrack( const KUrl &src, const KUrl &dst )
370
{
371
    m_wait = true;
372
373
    KIO::FileCopyJob *job = KIO::file_copy( src, dst,
374
            -1 /* permissions */,
375
            KIO::HideProgressInfo );
376
    connect( job, SIGNAL( result( KIO::Job * ) ),
377
            this,  SLOT( fileTransferred( KIO::Job * ) ) );
378
379
    bool tryToRemove = false;
380
    while ( m_wait )
381
    {
382
        if( isCanceled() )
383
        {
384
            job->kill( KJob::EmitResult );
385
            tryToRemove = true;
386
            m_wait = false;
387
        }
388
        else
389
        {
390
            usleep(10000);
391
            kapp->processEvents( QEventLoop::ExcludeUserInputEvents );
392
        }
393
    }
394
395
    if( !tryToRemove )
396
    {
397
        if(m_copyFailed)
398
        {
399
            tryToRemove = true;
400
            The::statusBar()->longMessage(
401
                    i18n( "Media Device: Copying %1 to %2 failed",
402
                          src.prettyUrl(),
403
                          dst.prettyUrl()
404
                        ),
405
                    StatusBar::Error );
406
        }
407
        else
408
        {
409
            MetaFile::Track track2(dst);
410
            if( !track2.isPlayable() && track2.filesize()==0 )
411
            {
412
                tryToRemove = true;
413
                // probably s.th. went wrong
414
                The::statusBar()->longMessage(
415
                        i18n( "Media Device: Reading tags from %1 failed", dst.prettyUrl() ),
416
                        StatusBar::Error );
417
            }
418
        }
419
    }
420
421
    if( tryToRemove )
422
    {
423
        QFile::remove( dst.path() );
424
        return false;
425
    }
426
427
    return true;
428
}
429
430
void
431
MediaDevice::fileTransferred( KIO::Job *job )  //SLOT
432
{
433
    if(job->error())
434
    {
435
        m_copyFailed = true;
436
        debug() << "file transfer failed: " << job->errorText();
437
    }
438
    else
439
    {
440
        m_copyFailed = false;
441
    }
442
443
    m_wait = false;
444
}
445
446
bool
447
MediaDevice::connectDevice( bool silent )
448
{
449
    if( !lockDevice( true ) )
450
        return false;
451
452
    runPreConnectCommand();
453
    openDevice( silent );
454
455
    if( isConnected()
456
            && MediaBrowser::instance()->currentDevice() != this
457
            && MediaBrowser::instance()->currentDevice()
458
            && !MediaBrowser::instance()->currentDevice()->isConnected() )
459
    {
460
        MediaBrowser::instance()->activateDevice( this );
461
    }
462
    m_parent->updateStats();
463
    m_parent->updateButtons();
464
465
    if( !isConnected() )
466
    {
467
        unlockDevice();
468
        return false;
469
    }
470
471
    if( m_syncStats )
472
    {
473
        syncStatsFromDevice( 0 );
474
        //Scrobbler::instance()->m_submitter->syncComplete();
475
    }
476
477
    // delete podcasts already played
478
    if( m_autoDeletePodcasts && m_podcastItem )
479
    {
480
        QList<MediaItem*> list;
481
        //NOTE we assume that currentItem is the main target
482
        int numFiles  = m_view->getSelectedLeaves( m_podcastItem, &list, MediaView::OnlyPlayed );
483
484
        if(numFiles > 0)
485
        {
486
            m_parent->m_stats->setText( i18np( "1 track to be deleted", "%1 tracks to be deleted", numFiles ) );
487
488
            setProgress( 0, numFiles );
489
490
            int numDeleted = deleteItemFromDevice( m_podcastItem, true );
491
            purgeEmptyItems();
492
            if( numDeleted < 0 )
493
            {
494
                The::statusBar()->longMessage(
495
                        i18n( "Failed to purge podcasts already played" ),
496
                        StatusBar::Sorry );
497
            }
498
            else if( numDeleted > 0 )
499
            {
500
                The::statusBar()->shortMessage(
501
                        i18np( "Purged 1 podcasts already played",
502
                            "Purged %1 podcasts already played",
503
                            numDeleted ) );
504
            }
505
506
            synchronizeDevice();
507
508
            QTimer::singleShot( 1500, m_parent->m_progressBox, SLOT(hide()) );
509
            m_parent->queue()->computeSize();
510
            m_parent->updateStats();
511
        }
512
    }
513
    unlockDevice();
514
515
    updateRootItems();
516
517
    if( m_deferredDisconnect )
518
    {
519
        m_deferredDisconnect = false;
520
        disconnectDevice( m_runDisconnectHook );
521
    }
522
523
    The::statusBar()->shortMessage( i18n( "Device successfully connected" ) );
524
525
    m_parent->updateDevices();
526
527
    return true;
528
}
529
530
bool
531
MediaDevice::disconnectDevice( bool postDisconnectHook )
532
{
533
    DEBUG_BLOCK
534
535
    abortTransfer();
536
537
    debug() << "disconnecting: hook=" << postDisconnectHook;
538
539
    if( !lockDevice( true ) )
540
    {
541
        m_runDisconnectHook = postDisconnectHook;
542
        m_deferredDisconnect = true;
543
        debug() << "disconnecting: locked";
544
        return false;
545
    }
546
    debug() << "disconnecting: ok";
547
548
    if( m_syncStats )
549
    {
550
        syncStatsToDevice();
551
    }
552
553
    closeDevice();
554
    unlockDevice();
555
556
    m_parent->updateStats();
557
558
    bool result = true;
559
    if( postDisconnectHook && runPostDisconnectCommand() != 0 )
560
    {
561
        The::statusBar()->longMessage(
562
                i18n( "Post-disconnect command failed, before removing device, please make sure that it is safe to do so." ),
563
                StatusBar::Information );
564
        result = false;
565
    }
566
    else
567
        The::statusBar()->shortMessage( i18n( "Device successfully disconnected" ) );
568
569
    m_parent->updateDevices();
570
571
    return result;
572
}
573
574
void
575
MediaDevice::syncStatsFromDevice( MediaItem *root )
576
{
577
    MediaItem *it = static_cast<MediaItem *>( m_view->firstChild() );
578
    if( root )
579
    {
580
        it = static_cast<MediaItem *>( root->firstChild() );
581
    }
582
583
    kapp->processEvents( QEventLoop::ExcludeUserInputEvents );
584
585
    for( ; it; it = static_cast<MediaItem *>( it->nextSibling() ) )
586
    {
587
        switch( it->type() )
588
        {
589
        case MediaItem::TRACK:
590
            if( !it->parent() || static_cast<MediaItem *>( it->parent() )->type() != MediaItem::PLAYLIST )
591
            {
592
/* TODO port to meta
593
                const MetaBundle *bundle = it->bundle();
594
                for( int i=0; i<it->recentlyPlayed(); i++ )
595
                {
596
                    // submit to last.fm
597
                    if( bundle->length() > 30
598
                            && !bundle->artist().isEmpty() && bundle->artist() != i18n( "Unknown" )
599
                            && !bundle->title().isEmpty() && bundle->title() != i18n( "Unknown" ) )
600
                    {
601
                        // don't submit tracks shorter than 30 sec or w/o artist/title
602
                        debug() << "scrobbling " << bundle->artist() << " - " << bundle->title();
603
                        SubmitItem *sit = new SubmitItem( bundle->artist(), bundle->album(), bundle->title(), bundle->length(), false * fake time  );
604
                        Scrobbler::instance()->m_submitter->submitItem( sit );
605
                    }
606
607
                    // increase Amarok playcount
608
                    QString url = CollectionDB::instance()->getURL( *bundle );
609
                    if( !url.isEmpty() )
610
                    {
611
                        QDateTime t = it->playTime();
612
                        CollectionDB::instance()->addSongPercentage( url, 100, "mediadevice", t.isValid() ? &t : 0 );
613
                        debug() << "played " << url;
614
                    }
615
                }
616
617
                if( it->ratingChanged() )
618
                {
619
                    // copy rating from media device to Amarok
620
                    QString url = CollectionDB::instance()->getURL( *bundle );
621
                    debug() << "rating changed " << url << ": " << it->rating()/10;
622
                    if( !url.isEmpty() )
623
                    {
624
                        CollectionDB::instance()->setSongRating( url, it->rating()/10 );
625
                        it->setRating( it->rating() ); // prevent setting it again next time
626
                    }
627
                }*/
628
            }
629
            break;
630
        case MediaItem::PODCASTITEM:
631
//TODO port to meta
632
//            if( !it->parent() || static_cast<MediaItem *>( it->parent() )->type() != MediaItem::PLAYLIST )
633
//            {
634
//                const MetaBundle *bundle = it->bundle();
635
//                if( it->played() || it->recentlyPlayed() )
636
//                {
637
//                    if( PodcastEpisodeBundle *peb = bundle->podcastBundle() )
638
//                    {
639
//                        debug() << "marking podcast episode as played: " << peb->url();
640
//PORT 2.0
641
//                         if( PlaylistBrowser::instance() )
642
//                         {
643
//                             PodcastEpisode *p = PlaylistBrowser::instance()->findPodcastEpisode( peb->url(), peb->parent() );
644
//                             if ( p )
645
//                                 p->setListened();
646
//                             else
647
//                                 debug() << "did not find podcast episode: " << peb->url() << " from " << peb->parent();
648
//                         }
649
//                    }
650
//                }
651
//            }
652
            break;
653
654
        default:
655
            syncStatsFromDevice( it );
656
            break;
657
        }
658
    }
659
}
660
661
void
662
MediaDevice::syncStatsToDevice( MediaItem *root )
663
{
664
    MediaItem *it = static_cast<MediaItem *>( m_view->firstChild() );
665
    if( root )
666
    {
667
        it = static_cast<MediaItem *>( root->firstChild() );
668
    }
669
670
    kapp->processEvents( QEventLoop::ExcludeUserInputEvents );
671
672
    for( ; it; it = static_cast<MediaItem *>( it->nextSibling() ) )
673
    {
674
        switch( it->type() )
675
        {
676
        case MediaItem::TRACK:
677
            if( !it->parent() || static_cast<MediaItem *>( it->parent() )->type() != MediaItem::PLAYLIST )
678
            {
679
                //TODO port to meta
680
                //const MetaBundle *bundle = it->bundle();
681
                //QString url = CollectionDB::instance()->getURL( *bundle );
682
                //it->syncStatsFromPath( url );
683
            }
684
            break;
685
686
        case MediaItem::PODCASTITEM:
687
            if( !it->parent() || static_cast<MediaItem *>( it->parent() )->type() != MediaItem::PLAYLIST )
688
            {
689
                //TODO port to meta
690
                //const MetaBundle *bundle = it->bundle();
691
                //if( PodcastEpisodeBundle *peb = bundle->podcastBundle() )
692
                //{
693
// //PORT 2.0
694
//                     if( PlaylistBrowser::instance() )
695
//                     {
696
//                         PodcastEpisode *p = PlaylistBrowser::instance()->findPodcastEpisode( peb->url(), peb->parent() );
697
//                         if( p )
698
//                             it->setListened( !p->isNew() );
699
//                     }
700
                //}
701
            }
702
            break;
703
704
        default:
705
            syncStatsToDevice( it );
706
            break;
707
        }
708
    }
709
}
710
711
void
712
MediaDevice::transferFiles()
713
{
714
    if( !lockDevice( true ) )
715
    {
716
        return;
717
    }
718
719
    setCanceled( false );
720
721
    m_transferring = true;
722
    m_parent->transferAction()->setEnabled( false );
723
724
    setProgress( 0, m_parent->m_queue->childCount() );
725
726
    // ok, let's copy the stuff to the device
727
728
    KUrl::List existing, unplayable;
729
    unsigned transcodeFail = 0;
730
    // iterate through items
731
    MediaItem *next = static_cast<MediaItem *>(m_parent->m_queue->firstChild());
732
    while( next )
733
    {
734
        MediaItem *transferredItem = next;
735
        transferredItem->setFailed( false );
736
        transferredItem->m_flags |= MediaItem::Transferring;
737
        next = static_cast<MediaItem *>( transferredItem->nextSibling() );
738
739
        if( transferredItem->device() )
740
        {
741
            transferredItem->device()->copyTrackFromDevice( transferredItem );
742
            m_parent->m_queue->subtractItemFromSize( transferredItem, true );
743
            delete transferredItem;
744
            setProgress( progress() + 1 );
745
            m_parent->m_queue->itemCountChanged();
746
            kapp->processEvents( QEventLoop::ExcludeUserInputEvents );
747
            continue;
748
        }
749
750
        Meta::TrackList tracks;
751
        if( transferredItem->type() == MediaItem::PLAYLIST )
752
        {
753
            if( transferredItem->flags() & MediaItem::SmartPlaylist )
754
                tracks = tracksToSync( transferredItem->text( 0 ), transferredItem->data() );
755
            else
756
                tracks = tracksToSync( transferredItem->text( 0 ), KUrl( transferredItem->data() ) );
757
        }
758
        else if( Meta::TrackPtr::dynamicCast(transferredItem->meta()) )
759
            tracks.append( Meta::TrackPtr::dynamicCast(transferredItem->meta()) );
760
        else
761
        {
762
            // this should not happen
763
            debug() << "invalid item in transfer queue";
764
            m_parent->m_queue->subtractItemFromSize( transferredItem, true );
765
            delete transferredItem;
766
            m_parent->m_queue->itemCountChanged();
767
            continue;
768
        }
769
770
        if( tracks.count() > 1 )
771
            setProgress( progress(), MediaBrowser::instance()->m_progress->maximum() + tracks.count() - 1 );
772
773
        QString playlist = transferredItem->m_playlistName;
774
        for( Meta::TrackList::const_iterator it = tracks.constBegin(), end = tracks.constEnd();
775
                it != end;
776
                ++it )
777
        {
778
            if( isCanceled() )
779
                break;
780
781
            Meta::TrackPtr track = *it;
782
783
            bool transcoding = false;
784
            MediaItem *item = trackExists( track );
785
            if( item && playlist.isEmpty() )
786
            {
787
                The::statusBar()->shortMessage( i18n( "Track already on media device: %1",
788
                                                                           track->prettyUrl() ),
789
                        StatusBar::Sorry );
790
                existing += track->url();
791
                setProgress( progress() + 1 );
792
                continue;
793
            }
794
            else if( !item ) // the item does not yet exist on the media device
795
            {
796
                if( m_transcode && ( !isPlayable( track ) || m_transcodeAlways ) )
797
                {
798
                    QString preferred = supportedFiletypes().isEmpty() ? "mp3" : supportedFiletypes().first();
799
                    debug() << "transcoding " << track->url() << " to " << preferred;
800
                    KUrl transcoded = MediaBrowser::instance()->transcode( track->url(), preferred );
801
                    if( isCanceled() )
802
                        break;
803
                    if( transcoded.isEmpty() )
804
                    {
805
                        debug() << "transcoding failed";
806
                        transcodeFail++;
807
                    }
808
                    else
809
                    {
810
                        transcoding = true;
811
                        MetaFile::Track *transcodedTrack = new MetaFile::Track( transcoded );
812
                        transcodedTrack->setTitle( track->name() );
813
                        transcodedTrack->setArtist( track->artist()->name() );
814
                        transcodedTrack->setComposer( track->composer()->name() );
815
                        transcodedTrack->setAlbum( track->album()->name() );
816
                        transcodedTrack->setGenre( track->genre()->name() );
817
                        transcodedTrack->setComment( track->comment() );
818
                        transcodedTrack->setYear( track->year()->name() );
819
                        transcodedTrack->setDiscNumber( track->discNumber() );
820
                        transcodedTrack->setTrackNumber( track->trackNumber() );
821
                        //TODO port to meta
822
                        //if( bundle->podcastBundle() )
823
                        //{
824
                        //    transcodedBundle->setPodcastBundle( *bundle->podcastBundle() );
825
                        //    transcodedBundle->copyFrom( *bundle->podcastBundle() );
826
                        //}
827
                        track = Meta::TrackPtr( transcodedTrack );
828
                    }
829
                }
830
831
                if( !isPlayable( track ) )
832
                {
833
                    The::statusBar()->shortMessage( i18n( "Track not playable on media device: %1", track->prettyUrl() ),
834
                            StatusBar::Sorry );
835
                    unplayable += (*it)->url();
836
                    transferredItem->setFailed();
837
                    setProgress( progress() + 1 );
838
                    continue;
839
                }
840
                item = copyTrackToDevice( track );
841
            }
842
843
            if( !item ) // copyTrackToDevice() failed
844
            {
845
                if( !isCanceled() )
846
                {
847
                    The::statusBar()->longMessage(
848
                            i18n( "Failed to copy track to media device: %1", track->url() ),
849
                            StatusBar::Sorry );
850
                    transferredItem->setFailed();
851
                }
852
            }
853
854
            if( transcoding )
855
            {
856
                if( m_transcodeRemove )
857
                    QFile( track->url() ).remove();
858
            }
859
860
            if( isCanceled() )
861
                break;
862
863
            if( !item )
864
            {
865
                setProgress( progress() + 1 );
866
                continue;
867
            }
868
869
            item->syncStatsFromPath( (*it)->url() );
870
871
            if( m_playlistItem && !playlist.isEmpty() )
872
            {
873
                MediaItem *pl = m_playlistItem->findItem( playlist );
874
                if( !pl )
875
                {
876
                    QList<MediaItem*> items;
877
                    pl = newPlaylist( playlist, m_playlistItem, items );
878
                }
879
                if( pl )
880
                {
881
                    QList<MediaItem*> items;
882
                    items.append( item );
883
                    addToPlaylist( pl, pl->lastChild(), items );
884
                }
885
            }
886
887
            setProgress( progress() + 1 );
888
        }
889
890
        transferredItem->m_flags &= ~MediaItem::Transferring;
891
892
        if( isCanceled() )
893
        {
894
            m_parent->updateStats();
895
            break;
896
        }
897
898
        if( !(transferredItem->flags() & MediaItem::Failed) )
899
        {
900
            m_parent->m_queue->subtractItemFromSize( transferredItem, true );
901
            delete transferredItem;
902
            m_parent->m_queue->itemCountChanged();
903
        }
904
        m_parent->updateStats();
905
906
        kapp->processEvents( QEventLoop::ExcludeUserInputEvents );
907
    }
908
    synchronizeDevice();
909
    unlockDevice();
910
    fileTransferFinished();
911
912
    QString msg;
913
    if( unplayable.count() > 0 )
914
    {
915
        msg = i18np( "One track not playable on media device",
916
                "%1 tracks not playable on media device", unplayable.count() );
917
    }
918
    if( existing.count() > 0 )
919
    {
920
        if( msg.isEmpty() )
921
            msg = i18np( "One track already on media device",
922
                    "%1 tracks already on media device", existing.count() );
923
        else
924
            msg += i18np( ", one track already on media device",
925
                    ", %1 tracks already on media device", existing.count() );
926
    }
927
    if( transcodeFail > 0 )
928
    {
929
        if( msg.isEmpty() )
930
            msg = i18np( "One track was not transcoded",
931
                    "%1 tracks were not transcoded", transcodeFail );
932
        else
933
            msg += i18np( ", one track was not transcoded",
934
                    ", %1 tracks were not transcoded", transcodeFail );
935
936
        const ScriptManager* const sm = ScriptManager::instance();
937
        if( !sm->transcodeScriptRunning().isEmpty() )
938
            msg += i18n( " (no transcode script running)" );
939
    }
940
941
    if( unplayable.count() + existing.count() > 0 )
942
    {
943
        QString longMsg = i18n( "The following tracks were not transferred: ");
944
        for( KUrl::List::ConstIterator it = existing.constBegin(), end = existing.constEnd();
945
                it != end;
946
                ++it )
947
        {
948
            longMsg += "<br>" + (*it).prettyUrl();
949
        }
950
        for( KUrl::List::ConstIterator it = unplayable.constBegin(), end = unplayable.constEnd();
951
                it != end;
952
                it++ )
953
        {
954
            longMsg += "<br>" + (*it).prettyUrl();
955
        }
956
        The::statusBar()->longMessage( longMsg, StatusBar::Sorry );
957
    }
958
    else if( !msg.isEmpty() )
959
    {
960
        The::statusBar()->shortMessage( msg, StatusBar::Sorry );
961
    }
962
963
    m_parent->updateButtons();
964
    m_parent->queue()->save( Amarok::saveLocation() + "transferlist.xml" );
965
    m_transferring = false;
966
967
    if( m_deferredDisconnect )
968
    {
969
        m_deferredDisconnect = false;
970
        disconnectDevice( m_runDisconnectHook );
971
    }
972
    else if( m_scheduledDisconnect )
973
    {
974
        disconnectDevice( true );
975
    }
976
    m_scheduledDisconnect = false;
977
}
978
979
int
980
MediaDevice::progress() const
981
{
982
    return m_parent->m_progress->value();
983
}
984
985
void
986
MediaDevice::setProgress( const int progress, const int total )
987
{
988
    if( total != -1 )
989
        m_parent->m_progress->setRange( 0, total );
990
    m_parent->m_progress->setValue( progress );
991
    m_parent->m_progressBox->show();
992
}
993
994
void
995
MediaDevice::fileTransferFinished()  //SLOT
996
{
997
    m_parent->updateStats();
998
    m_parent->m_progressBox->hide();
999
    m_parent->transferAction()->setEnabled( isConnected() && m_parent->queue()->childCount() > 0 );
1000
    m_wait = false;
1001
}
1002
1003
1004
int
1005
MediaDevice::deleteFromDevice(MediaItem *item, int flags )
1006
{
1007
    MediaItem* fi = item;
1008
    int count = 0;
1009
1010
    if ( !(flags & Recursing) )
1011
    {
1012
        if( !lockDevice( true ) )
1013
            return 0;
1014
1015
        setCanceled( false );
1016
1017
        m_deleting = true;
1018
1019
        QList<MediaItem*> list;
1020
        //NOTE we assume that currentItem is the main target
1021
        int numFiles  = m_view->getSelectedLeaves(item, &list, MediaView::OnlySelected | ((flags & OnlyPlayed) ? MediaView::OnlyPlayed : MediaView::None) );
1022
1023
        m_parent->m_stats->setText( i18np( "1 track to be deleted", "%1 tracks to be deleted", numFiles ) );
1024
        if( numFiles > 0 && (flags & DeleteTrack) )
1025
        {
1026
            int button = KMessageBox::warningContinueCancel( m_parent,
1027
                    i18np( "<p>You have selected 1 track to be <b>irreversibly</b> deleted.</p>",
1028
                        "<p>You have selected %1 tracks to be <b>irreversibly</b> deleted.</p>",
1029
                        numFiles
1030
                        ),
1031
                    QString(),
1032
                    KGuiItem(i18n("&Delete"),"edit-delete") );
1033
1034
            if ( button != KMessageBox::Continue )
1035
            {
1036
                m_parent->queue()->computeSize();
1037
                m_parent->updateStats();
1038
                m_deleting = false;
1039
                unlockDevice();
1040
                return 0;
1041
            }
1042
1043
            if(!isTransferring())
1044
            {
1045
                setProgress( 0, numFiles );
1046
            }
1047
1048
        }
1049
        // don't return if numFiles==0: playlist items might be to delete
1050
1051
        if( !fi )
1052
            fi = static_cast<MediaItem*>(m_view->firstChild());
1053
    }
1054
1055
    while( fi )
1056
    {
1057
        MediaItem *next = static_cast<MediaItem*>(fi->nextSibling());
1058
1059
        if( isCanceled() )
1060
        {
1061
            break;
1062
        }
1063
1064
        if( !fi->isVisible() )
1065
        {
1066
            fi = next;
1067
            continue;
1068
        }
1069
1070
        if( fi->isSelected() )
1071
        {
1072
            int ret = deleteItemFromDevice(fi, flags);
1073
            if( ret >= 0 && count >= 0 )
1074
                count += ret;
1075
            else
1076
                count = -1;
1077
        }
1078
        else
1079
        {
1080
            if( fi->childCount() )
1081
            {
1082
                int ret = deleteFromDevice( static_cast<MediaItem*>(fi->firstChild()), flags | Recursing );
1083
                if( ret >= 0 && count >= 0 )
1084
                    count += ret;
1085
                else
1086
                    count = -1;
1087
            }
1088
        }
1089
        m_parent->updateStats();
1090
1091
        fi = next;
1092
    }
1093
1094
    if(!(flags & Recursing))
1095
    {
1096
        purgeEmptyItems();
1097
        synchronizeDevice();
1098
        m_deleting = false;
1099
        unlockDevice();
1100
1101
        if(!isTransferring())
1102
        {
1103
            QTimer::singleShot( 1500, m_parent->m_progressBox, SLOT(hide()) );
1104
        }
1105
1106
        if( m_deferredDisconnect )
1107
        {
1108
            m_deferredDisconnect = false;
1109
            disconnectDevice( m_runDisconnectHook );
1110
        }
1111
    }
1112
    m_parent->queue()->computeSize();
1113
    m_parent->updateStats();
1114
1115
    return count;
1116
}
1117
1118
void
1119
MediaDevice::purgeEmptyItems( MediaItem *root )
1120
{
1121
    MediaItem *it = 0;
1122
    if( root )
1123
    {
1124
        it = static_cast<MediaItem *>(root->firstChild());
1125
    }
1126
    else
1127
    {
1128
        it = static_cast<MediaItem *>(m_view->firstChild());
1129
    }
1130
1131
    MediaItem *next = 0;
1132
    for( ; it; it=next )
1133
    {
1134
        next = static_cast<MediaItem *>(it->nextSibling());
1135
        purgeEmptyItems( it );
1136
        if( it->childCount() == 0 &&
1137
                (it->type() == MediaItem::ARTIST ||
1138
                 it->type() == MediaItem::ALBUM ||
1139
                 it->type() == MediaItem::PODCASTCHANNEL) )
1140
            delete it;
1141
    }
1142
}
1143
1144
bool
1145
MediaDevice::isPlayable( const Meta::TrackPtr track )
1146
{
1147
    if ( track.isNull() )
1148
        return false;
1149
1150
    if( supportedFiletypes().isEmpty() )
1151
        return true;
1152
1153
    QString type = track->url().section( '.', -1 ).toLower();
1154
    return supportedFiletypes().contains( type );
1155
}
1156
1157
bool
1158
MediaDevice::isPreferredFormat( const Meta::TrackPtr track )
1159
{
1160
    if( track.isNull() )
1161
        return true;
1162
1163
    if( supportedFiletypes().isEmpty() )
1164
        return true;
1165
1166
    QString type = track->url().section( '.', -1 ).toLower();
1167
    return ( type == supportedFiletypes().first() );
1168
}
1169
1170
#include "MediaDevice.moc"