1
/****************************************************************************************
2
 * Copyright (c) 2002 Mark Kretschmann <kretschmann@kde.org>                            *
3
 *                                                                                      *
4
 * This program is free software; you can redistribute it and/or modify it under        *
5
 * the terms of the GNU General Public License as published by the Free Software        *
6
 * Foundation; either version 2 of the License, or (at your option) any later           *
7
 * version.                                                                             *
8
 *                                                                                      *
9
 * This program is distributed in the hope that it will be useful, but WITHOUT ANY      *
10
 * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A      *
11
 * PARTICULAR PURPOSE. See the GNU General Public License for more details.             *
12
 *                                                                                      *
13
 * You should have received a copy of the GNU General Public License along with         *
14
 * this program.  If not, see <http://www.gnu.org/licenses/>.                           *
15
 ****************************************************************************************/
16
17
#include "App.h"
18
19
#include <config-amarok.h>
20
21
#include "core/support/Amarok.h"
22
#include "amarokconfig.h"
23
#include "amarokurls/AmarokUrl.h"
24
#include "core-impl/collections/support/CollectionManager.h"
25
#include "core/support/Components.h"
26
#include "core/interfaces/Logger.h"
27
#include "ConfigDialog.h"
28
#include "covermanager/CoverFetcher.h"
29
#include "dialogs/EqualizerDialog.h"
30
#include "dbus/CollectionDBusHandler.h"
31
#include "core/support/Debug.h"
32
#include "EngineController.h"
33
#include "firstruntutorial/FirstRunTutorial.h"
34
#include "KNotificationBackend.h"
35
#include "core/capabilities/SourceInfoCapability.h"
36
#include "core/meta/support/MetaConstants.h"
37
#include "core/meta/Meta.h"
38
#include "core/meta/support/MetaUtility.h"
39
#include "network/NetworkAccessManagerProxy.h"
40
#include "Osd.h"
41
#include "PlaybackConfig.h"
42
#include "dbus/mpris1/PlayerHandler.h"
43
#include "core/playlists/Playlist.h"
44
#include "core/playlists/PlaylistFormat.h"
45
#include "core-impl/playlists/types/file/PlaylistFileSupport.h"
46
#include "playlist/PlaylistActions.h"
47
#include "playlist/PlaylistModelStack.h"
48
#include "playlist/PlaylistController.h"
49
#include "playlistmanager/PlaylistManager.h"
50
#include "core/plugins/PluginManager.h"
51
#include "core/podcasts/PodcastProvider.h"
52
#include "dbus/mpris1/RootHandler.h"
53
#include "ScriptManager.h"
54
#include "statemanagement/ApplicationController.h"
55
#include "statemanagement/DefaultApplicationController.h"
56
#include "dbus/mpris1/TrackListHandler.h"
57
#ifdef HAVE_KSTATUSNOTIFIERITEM
58
#include "TrayIcon.h"
59
#else
60
#include "TrayIconLegacy.h"
61
#endif
62
63
#ifdef NO_MYSQL_EMBEDDED
64
#include "MySqlServerTester.h"
65
#endif
66
67
#include <iostream>
68
69
#include <KAction>
70
#include <KCalendarSystem>
71
#include <KCmdLineArgs>                  //initCliArgs()
72
#include <KDirLister>
73
#include <KEditToolBar>                  //slotConfigToolbars()
74
#include <KGlobalSettings>
75
#include <KIO/CopyJob>
76
#include <KJob>
77
#include <KJobUiDelegate>
78
#include <KLocale>
79
#include <KMessageBox>
80
#include <KShortcutsDialog>              //slotConfigShortcuts()
81
#include <KStandardDirs>
82
83
#include <QByteArray>
84
#include <QDesktopServices>
85
#include <QFile>
86
#include <KPixmapCache>
87
#include <QStringList>
88
#include <QTextDocument>                // for Qt::escape()
89
#include <QTimer>                       //showHyperThreadingWarning()
90
#include <QtDBus/QtDBus>
91
92
#include "shared/taglib_filetype_resolvers/asffiletyperesolver.h"
93
#include "shared/taglib_filetype_resolvers/mimefiletyperesolver.h"
94
#include "shared/taglib_filetype_resolvers/mp4filetyperesolver.h"
95
#include "shared/taglib_filetype_resolvers/wavfiletyperesolver.h"
96
#include <audiblefiletyperesolver.h>
97
#include <realmediafiletyperesolver.h>
98
99
int App::mainThreadId = 0;
100
101
#ifdef Q_WS_MAC
102
#include <CoreFoundation/CoreFoundation.h>
103
extern void setupEventHandler_mac(long);
104
#endif
105
106
#ifdef DEBUG
107
#include "TestDirectoryLoader.h"
108
#endif // DEBUG
109
110
QStringList App::s_delayedAmarokUrls = QStringList();
111
112
AMAROK_EXPORT KAboutData aboutData( "amarok", 0,
113
    ki18n( "Amarok" ), AMAROK_VERSION,
114
    ki18n( "The audio player for KDE" ), KAboutData::License_GPL,
115
    ki18n( "(C) 2002-2003, Mark Kretschmann\n(C) 2003-2010, The Amarok Development Squad" ),
116
    ki18n( "IRC:\nirc.freenode.net - #amarok, #amarok.de, #amarok.es, #amarok.fr\n\nFeedback:\namarok@kde.org\n\n(Build Date: %1)" ).subs( __DATE__ ),
117
             ( "http://amarok.kde.org" ) );
118
119
AMAROK_EXPORT OcsData ocsData( "opendesktop" );
120
121
App::App()
122
        : KUniqueApplication()
123
        , m_tray(0)
124
{
125
    DEBUG_BLOCK
126
    PERF_LOG( "Begin Application ctor" )
127
128
    // required for last.fm plugin to grab app version
129
    setApplicationVersion( AMAROK_VERSION );
130
131
    PERF_LOG( "Registering taglib plugins" )
132
    TagLib::FileRef::addFileTypeResolver(new RealMediaFileTypeResolver);
133
    TagLib::FileRef::addFileTypeResolver(new AudibleFileTypeResolver);
134
    TagLib::FileRef::addFileTypeResolver(new WAVFileTypeResolver);
135
    TagLib::FileRef::addFileTypeResolver(new MP4FileTypeResolver);
136
    TagLib::FileRef::addFileTypeResolver(new ASFFileTypeResolver);
137
    TagLib::FileRef::addFileTypeResolver(new MimeFileTypeResolver);
138
    PERF_LOG( "Done Registering taglib plugins" )
139
140
    qRegisterMetaType<Meta::DataPtr>();
141
    qRegisterMetaType<Meta::DataList>();
142
    qRegisterMetaType<Meta::TrackPtr>();
143
    qRegisterMetaType<Meta::TrackList>();
144
    qRegisterMetaType<Meta::AlbumPtr>();
145
    qRegisterMetaType<Meta::AlbumList>();
146
    qRegisterMetaType<Meta::ArtistPtr>();
147
    qRegisterMetaType<Meta::ArtistList>();
148
    qRegisterMetaType<Meta::GenrePtr>();
149
    qRegisterMetaType<Meta::GenreList>();
150
    qRegisterMetaType<Meta::ComposerPtr>();
151
    qRegisterMetaType<Meta::ComposerList>();
152
    qRegisterMetaType<Meta::YearPtr>();
153
    qRegisterMetaType<Meta::YearList>();
154
    qRegisterMetaType<Meta::LabelPtr>();
155
    qRegisterMetaType<Meta::LabelList>();
156
157
158
    //make sure we have enough cache space for all our crazy svg stuff
159
    KPixmapCache cache( "Amarok-pixmaps" );
160
    cache.setCacheLimit ( 20 * 1024 );
161
162
#ifdef Q_WS_MAC
163
    // this is inspired by OpenSceneGraph: osgDB/FilePath.cpp
164
165
    // Start with the Bundle PlugIns directory.
166
167
    // Get the main bundle first. No need to retain or release it since
168
    //  we are not keeping a reference
169
    CFBundleRef myBundle = CFBundleGetMainBundle();
170
    if( myBundle )
171
    {
172
        // CFBundleGetMainBundle will return a bundle ref even if
173
        //  the application isn't part of a bundle, so we need to
174
        //  check
175
        //  if the path to the bundle ends in ".app" to see if it is
176
        //  a
177
        //  proper application bundle. If it is, the plugins path is
178
        //  added
179
        CFURLRef urlRef = CFBundleCopyBundleURL(myBundle);
180
        if(urlRef)
181
        {
182
            char bundlePath[1024];
183
            if( CFURLGetFileSystemRepresentation( urlRef, true, (UInt8 *)bundlePath, sizeof(bundlePath) ) )
184
            {
185
                QByteArray bp( bundlePath );
186
                size_t len = bp.length();
187
                if( len > 4 && bp.right( 4 ) == ".app" )
188
                {
189
                    bp.append( "/Contents/MacOS" );
190
                    QByteArray path = qgetenv( "PATH" );
191
                    if( path.length() > 0 )
192
                    {
193
                        path.prepend( ":" );
194
                    }
195
                    path.prepend( bp );
196
                    debug() << "setting PATH=" << path;
197
                    setenv("PATH", path, 1);
198
                }
199
            }
200
            // docs say we are responsible for releasing CFURLRef
201
            CFRelease(urlRef);
202
        }
203
    }
204
205
    setupEventHandler_mac((long)this);
206
#endif
207
208
    PERF_LOG( "Done App ctor" )
209
210
    continueInit();
211
}
212
213
App::~App()
214
{
215
    DEBUG_BLOCK
216
217
    CollectionManager::instance()->stopScan();
218
219
    // Hiding the OSD before exit prevents crash
220
    Amarok::OSD::instance()->hide();
221
222
    // This following can't go in the PlaylistModel destructor, because by the time that
223
    // happens, the Config has already been written.
224
225
    // Use the bottom model because that provides the most dependable/invariable row
226
    // number to save in an external file.
227
    AmarokConfig::setLastPlaying( Playlist::ModelStack::instance()->bottom()->activeRow() );
228
229
    if ( AmarokConfig::resumePlayback() )
230
    {
231
        Meta::TrackPtr engineTrack = The::engineController()->currentTrack();
232
        if( engineTrack )
233
        {
234
            AmarokConfig::setResumeTrack( engineTrack->playableUrl().prettyUrl() );
235
            AmarokConfig::setResumeTime( The::engineController()->trackPositionMs() );
236
        }
237
        else
238
            AmarokConfig::setResumeTrack( QString() ); //otherwise it'll play previous resume next time!
239
    }
240
241
    The::engineController()->endSession(); //records final statistics
242
243
#ifndef Q_WS_MAC
244
    // do even if trayicon is not shown, it is safe
245
    Amarok::config().writeEntry( "HiddenOnExit", mainWindow()->isHidden() );
246
    AmarokConfig::self()->writeConfig();
247
#else
248
    // for some reason on OS X the main window always reports being hidden
249
    // this means if you have the tray icon enabled, amarok will always open minimized
250
    Amarok::config().writeEntry( "HiddenOnExit", false );
251
    AmarokConfig::self()->writeConfig();
252
#endif
253
254
    ScriptManager::destroy();
255
256
    // this must be deleted before the connection to the Xserver is
257
    // severed, or we risk a crash when the QApplication is exited,
258
    // I asked Trolltech! *smug*
259
    Amarok::OSD::destroy();
260
    Amarok::KNotificationBackend::destroy();
261
262
    AmarokConfig::self()->writeConfig();
263
264
    //mainWindow()->deleteBrowsers();
265
    delete mainWindow();
266
267
    Playlist::ModelStack::destroy();
268
    Playlist::Actions::destroy();
269
    PlaylistManager::destroy();
270
    CollectionManager::destroy();
271
    CoverFetcher::destroy();
272
    NetworkAccessManagerProxy::destroy();
273
274
    //this should be moved to App::quit() I guess
275
    Amarok::Components::applicationController()->shutdown();
276
277
278
#ifdef Q_WS_WIN
279
    // work around for KUniqueApplication being not completely implemented on windows
280
    QDBusConnectionInterface* dbusService;
281
    if (QDBusConnection::sessionBus().isConnected() && (dbusService = QDBusConnection::sessionBus().interface()))
282
    {
283
        dbusService->unregisterService("org.mpris.amarok");
284
    }
285
#endif
286
}
287
288
void
289
App::handleCliArgs() //static
290
{
291
    DEBUG_BLOCK
292
293
    KCmdLineArgs* const args = KCmdLineArgs::parsedArgs();
294
295
    if( args->isSet( "cwd" ) )
296
        KCmdLineArgs::setCwd( args->getOption( "cwd" ).toLocal8Bit() );
297
298
    bool haveArgs = true; // assume having args in first place
299
    if( args->count() > 0 )
300
    {
301
        KUrl::List list;
302
        for( int i = 0; i < args->count(); i++ )
303
        {
304
            KUrl url = args->url( i );
305
            //TODO:PORTME
306
            if( Podcasts::PodcastProvider::couldBeFeed( url.url() ) )
307
            {
308
                KUrl feedUrl = Podcasts::PodcastProvider::toFeedUrl( url.url() );
309
                The::playlistManager()->defaultPodcasts()->addPodcast( feedUrl );
310
            }
311
            else if( url.protocol() == "amarok" )
312
            {
313
                s_delayedAmarokUrls.append( url.url() );
314
            }
315
            else
316
            {
317
                list << url;
318
                DEBUG_LINE_INFO
319
            }
320
        }
321
322
        int options = Playlist::AppendAndPlay;
323
        if( args->isSet( "queue" ) )
324
           options = Playlist::Queue;
325
        else if( args->isSet( "append" ) )
326
           options = Playlist::Append;
327
        else if( args->isSet( "load" ) )
328
            options = Playlist::Replace;
329
330
        if( args->isSet( "play" ) )
331
            options |= Playlist::DirectPlay;
332
333
        The::playlistController()->insertOptioned( list, options );
334
    }
335
    else if ( args->isSet( "cdplay" ) )
336
        The::mainWindow()->playAudioCd();
337
338
    //we shouldn't let the user specify two of these since it is pointless!
339
    //so we prioritise, pause > stop > play > next > prev
340
    //thus pause is the least destructive, followed by stop as brakes are the most important bit of a car(!)
341
    //then the others seemed sensible. Feel free to modify this order, but please leave justification in the cvs log
342
    //I considered doing some sanity checks (eg only stop if paused or playing), but decided it wasn't worth it
343
    else if ( args->isSet( "pause" ) )
344
        The::engineController()->pause();
345
    else if ( args->isSet( "stop" ) )
346
        The::engineController()->stop();
347
    else if ( args->isSet( "play-pause" ) )
348
        The::engineController()->playPause();
349
    else if ( args->isSet( "play" ) ) //will restart if we are playing
350
        The::engineController()->play();
351
    else if ( args->isSet( "next" ) )
352
        The::playlistActions()->next();
353
    else if ( args->isSet( "previous" ) )
354
        The::playlistActions()->back();
355
    else // no args given
356
        haveArgs = false;
357
358
    static bool firstTime = true;
359
    const bool debugWasJustEnabled = !Amarok::config().readEntry( "Debug Enabled", false ) && args->isSet( "debug" );
360
    const bool debugIsDisabled = !args->isSet( "debug" );
361
    //allows debugging on OS X. Bundles have to be started with "open". Therefore it is not possible to pass an argument
362
    const bool forceDebug = Amarok::config().readEntry( "Force Debug", false );
363
364
365
    Amarok::config().writeEntry( "Debug Enabled", forceDebug ? true : args->isSet( "debug" ) );
366
367
    // Debug output will only work from this point on. If Amarok was run without debug output before,
368
    // then a part of the output (until this point) will be missing. Inform the user about this:
369
    if( debugWasJustEnabled || forceDebug )
370
    {
371
        debug() << "************************************************************************************************************";
372
        debug() << "** DEBUGGING OUTPUT IS NOW ENABLED. PLEASE NOTE THAT YOU WILL ONLY SEE THE FULL OUTPUT ON THE NEXT START. **";
373
        debug() << "************************************************************************************************************";
374
    }
375
    else if( firstTime && debugIsDisabled )
376
    {
377
        Amarok::config().writeEntry( "Debug Enabled", true );
378
        debug() << "**********************************************************************************************";
379
        debug() << "** AMAROK WAS STARTED IN NORMAL MODE. IF YOU WANT TO SEE DEBUGGING INFORMATION, PLEASE USE: **";
380
        debug() << "** amarok --debug                                                                           **";
381
        debug() << "**********************************************************************************************";
382
        Amarok::config().writeEntry( "Debug Enabled", false );
383
    }
384
385
    if( !firstTime && !haveArgs )
386
    {
387
        // mainWindow() can be 0 if another instance is loading, see https://bugs.kde.org/show_bug.cgi?id=202713
388
        if( pApp->mainWindow() )
389
            pApp->mainWindow()->activate();
390
    }
391
392
    firstTime = false;
393
394
#ifdef DEBUG
395
    if( args->isSet( "test" ) )
396
    {
397
        bool ok;
398
        int verboseInt = args->getOption( "verbose" ).toInt( &ok );
399
        verboseInt     = ok ? verboseInt : 2;
400
401
        QStringList testOpt( "amarok" );
402
403
        QString verbosity;
404
        switch( verboseInt )
405
        {
406
        case 0:
407
            verbosity = "-silent";
408
            break;
409
        case 1:
410
            verbosity = "-v1";
411
            break;
412
        default:
413
        case 2:
414
            verbosity = "-v2";
415
            break;
416
        case 3:
417
            verbosity = "-vs";
418
            break;
419
        }
420
        testOpt << verbosity;
421
422
        const QString format = args->getOption( "format" );
423
        if( format == "xml" || format == "lightxml" )
424
            testOpt << QString( '-' + format );
425
426
        const bool _stdout = ( args->getOption( "output" ) == "log" ) ? false : true;
427
        runUnitTests( testOpt, _stdout );
428
    }
429
#endif // DEBUG
430
431
    args->clear();    //free up memory
432
}
433
434
435
/////////////////////////////////////////////////////////////////////////////////////
436
// INIT
437
/////////////////////////////////////////////////////////////////////////////////////
438
439
void
440
App::initCliArgs( int argc, char *argv[] )
441
{
442
    KCmdLineArgs::reset();
443
    KCmdLineArgs::init( argc, argv, &::aboutData ); //calls KCmdLineArgs::addStdCmdLineOptions()
444
    initCliArgs();
445
}
446
447
void
448
App::initCliArgs() //static
449
{
450
    // Update main.cpp (below KUniqueApplication::start() wrt instanceOptions) aswell if needed!
451
    KCmdLineOptions options;
452
453
    options.add("+[URL(s)]", ki18n( "Files/URLs to open" ));
454
    options.add("cdplay", ki18n("Immediately start playing an audio cd"));
455
    options.add("r");
456
    options.add("previous", ki18n( "Skip backwards in playlist" ));
457
    options.add("p");
458
    options.add("play", ki18n( "Start playing current playlist" ));
459
    options.add("t");
460
    options.add("play-pause", ki18n( "Play if stopped, pause if playing" ));
461
    options.add("pause", ki18n( "Pause playback" ));
462
    options.add("s");
463
    options.add("stop", ki18n( "Stop playback" ));
464
    options.add("f");
465
    options.add("next", ki18n( "Skip forwards in playlist" ));
466
    options.add(":", ki18n("Additional options:"));
467
    options.add("a");
468
    options.add("append", ki18n( "Append files/URLs to playlist" ));
469
    options.add("queue", ki18n("Queue URLs after the currently playing track"));
470
    options.add("l");
471
    options.add("load", ki18n("Load URLs, replacing current playlist"));
472
    options.add("d");
473
    options.add("debug", ki18n("Print verbose debugging information"));
474
    options.add("m");
475
    options.add("multipleinstances", ki18n("Allow running multiple Amarok instances"));
476
    options.add("cwd <directory>", ki18n( "Base for relative filenames/URLs" ));
477
#ifdef DEBUG
478
    options.add(":", ki18n("Unit test options:"));
479
    options.add("test", ki18n( "Run integrated unit tests" ) );
480
    options.add("output <dest>", ki18n( "Destination of test output: 'stdout', 'log'" ), "log" );
481
    options.add("format <type>", ki18n( "Format of test output: 'xml', 'lightxml', 'plaintext'" ), "xml" );
482
    options.add("verbose <level>", ki18n( "Verbosity from 0-3 (highest)" ), "2" );
483
#endif // DEBUG
484
485
    KCmdLineArgs::addCmdLineOptions( options );   //add our own options
486
}
487
488
489
/////////////////////////////////////////////////////////////////////////////////////
490
// METHODS
491
/////////////////////////////////////////////////////////////////////////////////////
492
493
#include <id3v1tag.h>
494
#include <tbytevector.h>
495
#include <QTextCodec>
496
#include <KGlobal>
497
498
//this class is only used in this module, so I figured I may as well define it
499
//here and save creating another header/source file combination
500
501
// Local version of taglib's QStringToTString macro. It is here, because taglib's one is
502
// not Qt3Support clean (uses QString::utf8()). Once taglib will be clean of qt3support
503
// it is safe to use QStringToTString again
504
#define Qt4QStringToTString(s) TagLib::String(s.toUtf8().data(), TagLib::String::UTF8)
505
506
class ID3v1StringHandler : public TagLib::ID3v1::StringHandler
507
{
508
    QTextCodec *m_codec;
509
510
    virtual TagLib::String parse( const TagLib::ByteVector &data ) const
511
    {
512
        return Qt4QStringToTString( m_codec->toUnicode( data.data(), data.size() ) );
513
    }
514
515
    virtual TagLib::ByteVector render( const TagLib::String &ts ) const
516
    {
517
        const QByteArray qcs = m_codec->fromUnicode( TStringToQString(ts) );
518
        return TagLib::ByteVector( qcs, (uint) qcs.length() );
519
    }
520
521
public:
522
    ID3v1StringHandler( int codecIndex )
523
            : m_codec( QTextCodec::codecForName( QTextCodec::availableCodecs().at( codecIndex ) ) )
524
    {
525
        debug() << "codec: " << m_codec;
526
        debug() << "codec-name: " << m_codec->name();
527
    }
528
529
    ID3v1StringHandler( QTextCodec *codec )
530
            : m_codec( codec )
531
    {
532
        debug() << "codec: " << m_codec;
533
        debug() << "codec-name: " << m_codec->name();
534
    }
535
536
    virtual ~ID3v1StringHandler()
537
    {}
538
};
539
540
#undef Qt4QStringToTString
541
542
//SLOT
543
void App::applySettings( bool firstTime )
544
{
545
    ///Called when the configDialog is closed with OK or Apply
546
547
    DEBUG_BLOCK
548
549
    if( AmarokConfig::showTrayIcon() && ! m_tray )
550
    {
551
        m_tray = new Amarok::TrayIcon( m_mainWindow );
552
    }
553
    else if( !AmarokConfig::showTrayIcon() && m_tray )
554
    {
555
        delete m_tray;
556
        m_tray = 0;
557
    }
558
559
    if( !firstTime ) // prevent OSD from popping up during startup
560
        Amarok::OSD::instance()->applySettings();
561
562
    //on startup we need to show the window, but only if it wasn't hidden on exit
563
    //and always if the trayicon isn't showing
564
    if( m_mainWindow )
565
    {
566
567
        if( ( firstTime && !Amarok::config().readEntry( "HiddenOnExit", false ) )
568
            || !AmarokConfig::showTrayIcon() )
569
        {
570
            PERF_LOG( "showing main window again" )
571
            m_mainWindow->show();
572
            PERF_LOG( "after showing mainWindow" )
573
        }
574
    }
575
576
    if( !firstTime )
577
        emit settingsChanged();
578
}
579
580
#ifdef DEBUG
581
//SLOT
582
void
583
App::runUnitTests( const QStringList options, bool _stdout )
584
{
585
    DEBUG_BLOCK
586
587
    QString logPath;
588
    if( !_stdout )
589
    {
590
        const QString location = Amarok::saveLocation( "testresults/" );
591
        const QString stamp    = QDateTime::currentDateTime().toString( "yyyy-MM-dd.HH-mm-ss" );
592
        logPath                = QDir::toNativeSeparators( location + stamp + "/" );
593
594
        // create log folder for this run:
595
        QDir logDir( logPath );
596
        logDir.mkpath( logPath );
597
598
        QFile::remove( QDir::toNativeSeparators( Amarok::saveLocation( "testresults/" ) + "LATEST" ) );
599
        QFile::link( logPath, QDir::toNativeSeparators( Amarok::saveLocation( "testresults/" ) + "LATEST" ) );
600
601
        QFile logArgs( logPath + "test_options" );
602
        if( logArgs.open( QIODevice::WriteOnly ) )
603
        {
604
            logArgs.write( options.join( " " ).toLatin1() );
605
            logArgs.close();
606
        }
607
    }
608
609
    PERF_LOG( "Running Unit Tests" )
610
611
    // modifies the playlist asynchronously, so run this last to avoid messing other test results
612
    TestDirectoryLoader        *test015 = new TestDirectoryLoader( options, logPath );
613
614
    PERF_LOG( "Done Running Unit Tests" )
615
    Q_UNUSED( test015 )
616
}
617
#endif // DEBUG
618
619
//SLOT
620
void
621
App::continueInit()
622
{
623
    DEBUG_BLOCK
624
625
    PERF_LOG( "Begin App::continueInit" )
626
    const KCmdLineArgs* const args = KCmdLineArgs::parsedArgs();
627
    const bool restoreSession = args->count() == 0 || args->isSet( "append" ) || args->isSet( "queue" )
628
                                || Amarok::config().readEntry( "AppendAsDefault", false );
629
630
    QTextCodec* utf8codec = QTextCodec::codecForName( "UTF-8" );
631
    QTextCodec::setCodecForCStrings( utf8codec ); //We need this to make CollectionViewItem showing the right characters.
632
633
    new Amarok::DefaultApplicationController();
634
    Amarok::Components::applicationController()->start();
635
636
    KSplashScreen* splash = 0;
637
    if( AmarokConfig::showSplashscreen() && !isSessionRestored() )
638
    {
639
        QPixmap splashimg( KGlobal::dirs()->findResource( "data", "amarok/images/splash_screen.jpg" ) );
640
        splash = new KSplashScreen( splashimg, Qt::WindowStaysOnTopHint );
641
        splash->show();
642
    }
643
644
    PERF_LOG( "Creating MainWindow" )
645
    m_mainWindow = new MainWindow();
646
    PERF_LOG( "Done creating MainWindow" )
647
648
    if ( AmarokConfig::showTrayIcon() ) {
649
        m_tray = new Amarok::TrayIcon( mainWindow() );
650
    }
651
652
    PERF_LOG( "Creating DBus handlers" )
653
    new Mpris1::RootHandler();
654
    new Mpris1::PlayerHandler();
655
    new Mpris1::TrackListHandler();
656
    new CollectionDBusHandler( this );
657
    QDBusConnection::sessionBus().registerService("org.mpris.amarok");
658
    PERF_LOG( "Done creating DBus handlers" )
659
660
    //DON'T DELETE THIS NEXT LINE or the app crashes when you click the X (unless we reimplement closeEvent)
661
    //Reason: in ~App we have to call the deleteBrowsers method or else we run afoul of refcount foobar in KHTMLPart
662
    //But if you click the X (not Action->Quit) it automatically kills MainWindow because KMainWindow sets this
663
    //for us as default (bad KMainWindow)
664
    mainWindow()->setAttribute( Qt::WA_DeleteOnClose, false );
665
    //init playlist window as soon as the database is guaranteed to be usable
666
667
    // Create engine, show TrayIcon etc.
668
    applySettings( true );
669
670
    // Must be created _after_ MainWindow.
671
    PERF_LOG( "Starting ScriptManager" )
672
    ScriptManager::instance();
673
    PERF_LOG( "ScriptManager started" )
674
675
    The::engineController()->setVolume( AmarokConfig::masterVolume() );
676
    The::engineController()->setMuted( AmarokConfig::muteState() );
677
678
    Amarok::KNotificationBackend::instance()->setEnabled( AmarokConfig::kNotifyEnabled() );
679
    Amarok::OSD::instance()->applySettings(); // Create after setting volume (don't show OSD for that)
680
681
682
    if( AmarokConfig::resumePlayback() && restoreSession && !args->isSet( "stop" ) ) {
683
        //restore session as long as the user didn't specify media to play etc.
684
        //do this after applySettings() so OSD displays correctly
685
        The::engineController()->restoreSession();
686
    }
687
688
    if( AmarokConfig::monitorChanges() )
689
        CollectionManager::instance()->checkCollectionChanges();
690
691
    // Restore keyboard shortcuts etc from config
692
    Amarok::actionCollection()->readSettings();
693
694
    if( splash ) // close splash correctly
695
    {
696
        splash->close();
697
        delete splash;
698
    }
699
700
    PERF_LOG( "App init done" )
701
    KConfigGroup config = KGlobal::config()->group( "General" );
702
703
    // NOTE: First Run Tutorial disabled for 2.1-beta1 release (too buggy / unfinished)
704
#if 0
705
    const bool firstruntut = config.readEntry( "FirstRunTutorial", true );
706
    debug() << "Checking whether to run first run tutorial..." << firstruntut;
707
    if( firstruntut )
708
    {
709
        config.writeEntry( "FirstRunTutorial", false );
710
        debug() << "Starting first run tutorial";
711
        FirstRunTutorial *frt = new FirstRunTutorial( mainWindow() );
712
        QTimer::singleShot( 1000, frt, SLOT( initOverlay() ) );
713
    }
714
#endif
715
716
#ifdef NO_MYSQL_EMBEDDED
717
    bool useServer = true;
718
    if( !AmarokConfig::useServer() )
719
    {
720
        useServer = false;
721
        AmarokConfig::setUseServer( true );
722
    }
723
    if( !MySqlServerTester::testSettings(
724
             AmarokConfig::host(),
725
             AmarokConfig::user(),
726
             AmarokConfig::password(),
727
             AmarokConfig::port()
728
         )
729
    )
730
    {
731
        KMessageBox::messageBox( 0, KMessageBox::Information,
732
                ( !useServer ? i18n( "The embedded database was not found; you must set up a database server connection.\nYou must restart Amarok after doing this." ) :
733
                              i18n( "The connection details for the database server were invalid.\nYou must enter correct settings and restart Amarok after doing this." ) ),
734
                i18n( "Database Error" ) );
735
        slotConfigAmarok( "DatabaseConfig" );
736
    }
737
    else
738
#endif
739
    {
740
        if( config.readEntry( "First Run", true ) )
741
        {
742
            const KUrl musicUrl = QDesktopServices::storageLocation( QDesktopServices::MusicLocation );
743
            const QString musicDir = musicUrl.toLocalFile( KUrl::RemoveTrailingSlash );
744
            const QDir dir( musicDir );
745
746
            int result = KMessageBox::No;
747
            if( dir.exists() && dir.isReadable() )
748
            {
749
                result = KMessageBox::questionYesNoCancel(
750
                    mainWindow(),
751
                    i18n( "A music path, %1, is set in System Settings.\nWould you like to use that as a collection folder?", musicDir )
752
                    );
753
            }
754
755
            KConfigGroup folderConf = Amarok::config( "Collection Folders" );
756
            bool useMusicLocation( false );
757
            switch( result )
758
            {
759
            case KMessageBox::Yes:
760
                if( CollectionManager::instance()->primaryCollection() )
761
                {
762
                    CollectionManager::instance()->primaryCollection()->setProperty( "collectionFolders", QStringList() << musicDir );
763
                    CollectionManager::instance()->startFullScan();
764
                    useMusicLocation = true;
765
                }
766
                break;
767
768
            case KMessageBox::No:
769
                slotConfigAmarok( "CollectionConfig" );
770
                break;
771
772
            default:
773
                break;
774
            }
775
            folderConf.writeEntry( "Use MusicLocation", useMusicLocation );
776
            config.writeEntry( "First Run", false );
777
        }
778
    }
779
780
    // Using QTimer, so that we won't block the GUI
781
    QTimer::singleShot( 0, this, SLOT( checkCollectionScannerVersion() ) );
782
783
    //and now we can run any amarokurls provided on startup, as all components should be initialized by now!
784
    foreach( const QString& urlString, s_delayedAmarokUrls )
785
    {
786
        AmarokUrl aUrl( urlString );
787
        aUrl.run();
788
    }
789
    s_delayedAmarokUrls.clear();
790
791
    QTimer::singleShot( 1500, this, SLOT( resizeMainWindow() ) );
792
}
793
794
795
void App::resizeMainWindow() // SLOT
796
{
797
    // HACK
798
    // This code works around a bug in KDE 4.5, which causes our Plasma applets to show
799
    // with a wrong initial size. Remove when this bug is fixed in Plasma.
800
    m_mainWindow->resize( m_mainWindow->width(), m_mainWindow->height() - 1 );
801
    m_mainWindow->resize( m_mainWindow->width(), m_mainWindow->height() + 1 );
802
}
803
804
805
void App::checkCollectionScannerVersion()  // SLOT
806
{
807
    DEBUG_BLOCK
808
809
    QProcess scanner;
810
811
    scanner.start( collectionScannerLocation(), QStringList( "--version" ) );
812
    scanner.waitForFinished();
813
814
    const QString version = scanner.readAllStandardOutput().trimmed();
815
816
    if( version != AMAROK_VERSION  )
817
    {
818
        KMessageBox::error( 0, i18n( "<p>The version of the 'amarokcollectionscanner' tool\n"
819
                                     "does not match your Amarok version.</p>"
820
                                     "<p>Please note that Collection Scanning may not work correctly.</p>" ) );
821
    }
822
}
823
824
QString App::collectionScannerLocation()  // static
825
{
826
    QString scannerPath = KStandardDirs::locate( "exe", "amarokcollectionscanner" );
827
828
    // If the binary is not in $PATH, then search in the application folder too
829
    if( scannerPath.isEmpty() )
830
        scannerPath = applicationDirPath() + QDir::separator() + "amarokcollectionscanner";
831
832
    return scannerPath;
833
}
834
835
void App::slotConfigAmarok( const QString& page )
836
{
837
    Amarok2ConfigDialog* dialog = static_cast<Amarok2ConfigDialog*>( KConfigDialog::exists( "settings" ) );
838
839
    if( !dialog )
840
    {
841
        //KConfigDialog didn't find an instance of this dialog, so lets create it :
842
        dialog = new Amarok2ConfigDialog( mainWindow(), "settings", AmarokConfig::self() );
843
844
        connect( dialog, SIGNAL( settingsChanged( const QString& ) ), SLOT( applySettings() ) );
845
    }
846
847
    dialog->show( page );
848
}
849
850
void App::slotConfigShortcuts()
851
{
852
    KShortcutsDialog::configure( Amarok::actionCollection(), KShortcutsEditor::LetterShortcutsAllowed, mainWindow() );
853
    AmarokConfig::self()->writeConfig();
854
}
855
856
KIO::Job *App::trashFiles( const KUrl::List &files )
857
{
858
    KIO::Job *job = KIO::trash( files );
859
    Amarok::Components::logger()->newProgressOperation( job, i18n("Moving files to trash") );
860
    connect( job, SIGNAL( result( KJob* ) ), this, SLOT( slotTrashResult( KJob* ) ) );
861
    return job;
862
}
863
864
void App::slotTrashResult( KJob *job )
865
{
866
    if( job->error() )
867
        job->uiDelegate()->showErrorMessage();
868
}
869
870
void App::quit()
871
{
872
    The::playlistManager()->completePodcastDownloads();
873
874
    emit prepareToQuit();
875
    /*
876
    if( MediaBrowser::instance() && MediaBrowser::instance()->blockQuit() )
877
    {
878
        // don't quit yet, as some media devices still have to finish transferring data
879
        QTimer::singleShot( 100, this, SLOT( quit() ) );
880
        return;
881
    }
882
    */
883
    KApplication::quit();
884
}
885
886
bool App::event( QEvent *event )
887
{
888
    switch( event->type() )
889
    {
890
        //allows Amarok to open files from the finder on OS X
891
        case QEvent::FileOpen:
892
        {
893
            QString file = static_cast<QFileOpenEvent*>( event )->file();
894
            //we are only going to receive local files here
895
            KUrl url( file );
896
            if( Playlists::isPlaylist( url ) )
897
            {
898
                Playlists::PlaylistPtr playlist =
899
                        Playlists::PlaylistPtr::dynamicCast( Playlists::loadPlaylistFile( url ) );
900
                The::playlistController()->insertOptioned( playlist, Playlist::AppendAndPlay );
901
            }
902
            else
903
            {
904
                Meta::TrackPtr track = CollectionManager::instance()->trackForUrl( url );
905
                The::playlistController()->insertOptioned( track, Playlist::AppendAndPlay );
906
            }
907
            return true;
908
        }
909
        default:
910
            return KUniqueApplication::event( event );
911
    }
912
}
913
914
int App::newInstance()
915
{
916
    DEBUG_BLOCK
917
918
    static bool first = true;
919
    if ( isSessionRestored() && first )
920
    {
921
        first = false;
922
        return 0;
923
    }
924
925
    first = false;
926
927
    handleCliArgs();
928
    return 0;
929
}
930
931
932
#include "App.moc"