98
108
CachedHttp::setCustomUserAgent( QString( "Last.fm Client " ) + LASTFM_CLIENT_VERSION );
99
109
CachedHttp::setCustomCachePath( MooseUtils::cachePath() );
101
// Start helper daemon _before_ we bind any sockets, otherwise they might
102
// end up being blocked by the child-process.
103
MooseUtils::installHelperApp();
105
111
// do this asap to prevent multiple instances instantiating
106
112
m_control = new QTcpServer( this );
107
connect( m_control, SIGNAL(newConnection()), SLOT(onControlConnect()) );
113
connect( m_control, SIGNAL( newConnection() ), SLOT( onControlConnect() ) );
109
115
// Try to use the default port, otherwise revert to using an available port
110
116
// and write the port we're using to the Settings. sendToInstance() will use
132
142
// this must be set before dialogs spawn in init() or whatever
133
143
m_user = new User( this );
135
//REFACTOR this class merged here!
145
// TODO this class merged here!
136
146
The::webService(); //init webservice stuff
137
connect( The::webService(), SIGNAL(result( Request* )), SLOT(onRequestReturned( Request* )) );
139
// HACK: this is only here to fix the bug where the initial wizard runs and we're behind a proxy.
140
// We need to have done these tests before running the VerifyUserRequest, otherwise the Http object
141
// will not pick an autodetected proxy if there is one. Refactor for 1.5.
142
if( !The::settings().isUseProxy() )
144
ProxyTestRequest* proxyOnTest = new ProxyTestRequest( true );
145
ProxyTestRequest* proxyOffTest = new ProxyTestRequest( false );
146
proxyOnTest->start();
147
proxyOffTest->start();
147
connect( The::webService(), SIGNAL( result( Request* ) ), SLOT( onRequestReturned( Request* ) ) );
150
149
// Scrobbler must be initialised before the listener, otherwise crashes might ensue
151
150
m_scrobbler = new ScrobblerManager( this );
168
167
new ITunesScript( this, m_listener );
171
m_fpCollector = new FingerprintCollector( 1, this );
170
m_fpCollector = new FingerprintCollector( 1 /*number of threads*/, this );
172
171
m_fpQueryer = new FingerprintQueryer( this );
173
172
connect( m_fpQueryer, SIGNAL( trackFingerprinted( TrackInfo, bool ) ),
174
173
SLOT( onFingerprintQueryDone( TrackInfo, bool ) ) );
176
175
m_radio = new Radio( this );
177
connect( m_radio, SIGNAL(stateChanged( RadioState )), SLOT(onRadioStateChanged( RadioState )) );
176
connect( m_radio, SIGNAL( stateChanged( RadioState ) ), SLOT( onRadioStateChanged( RadioState ) ) );
179
178
m_container = new Container;
180
connect( m_container, SIGNAL(becameVisible()), SLOT(fetchMetaData()) );
179
connect( m_container, SIGNAL( becameVisible() ), SLOT( fetchMetaData() ) );
182
181
// Look for expired cached files and remove them
183
182
new CachedHttpJanitor( MooseUtils::cachePath(), this );
206
210
foreach ( QString const arg, qApp->arguments().mid( 1 ) ) //skip arg[0] the appname+path
207
211
parseCommand( arg );
209
213
// Need to save the state from before we run the wizard as the wizard will change it
210
214
bool firstRunBeforeWizard = The::settings().isFirstRun();
217
// the installation of the plugin is done in the ctor
218
ITunesPluginInstaller pluginInstaller;
219
pluginInstaller.install();
212
223
if ( The::settings().isFirstRun() )
214
LOG( 3, "First run, launching config wizard\n" );
225
// HACK: this is only here to fix the bug where the initial wizard runs and we're behind a proxy.
226
// We need to have done these tests before running the VerifyUserRequest, otherwise the Http object
227
// will not pick an autodetected proxy if there is one. Refactor for 1.5.
228
if( The::settings().isFirstRun() && !The::settings().isUseProxy() )
230
ProxyTestRequest* proxyOnTest = new ProxyTestRequest( true );
231
ProxyTestRequest* proxyOffTest = new ProxyTestRequest( false );
232
proxyOnTest->start();
233
proxyOffTest->start();
236
LOGL( 3, "First run, launching config wizard" );
215
237
QFile( MooseUtils::savePath( "mediadevice.db" ) ).remove();
217
239
ConfigWizard wiz( NULL, ConfigWizard::Login );
218
241
if ( wiz.exec() == QDialog::Rejected )
220
243
// If user cancels config wizard, we need to exit
252
// We just installed the plugin for the first time
253
bool const needsBootstrap = pluginInstaller.needsTwiddlyBootstrap();
255
// The update wizard invites the user to upgrade the iTunes plugin, and
256
// it did this last time the client was invoked. iTunes has been
257
// stoppped, so the new plugin will be running when we Twiddly starts
258
// iTunes during the bootstrap
259
bool needsBootstrap = The::settings().weWereJustUpgraded();
262
//NOTE some of this code is duplicated in Container::updateCheckDone()
263
if ( needsBootstrap )
265
// the NULL is strange, and I have no idea why we do it here or above
266
// hopefully whoever did it in the first place will comment it :P
267
ConfigWizard( NULL, ConfigWizard::MediaDevice ).exec();
275
qDebug() << "Preferred application for lastfm url-scheme:" << UnicornUtils::preferredAppForUrlScheme( QUrl( "lastfm://" ) );
276
qDebug() << "Our app:" << MooseUtils::bundleDirPath();
278
bool b = ( MooseUtils::bundleDirPath() == UnicornUtils::preferredAppForUrlScheme( QUrl( "lastfm://" ) ) );
281
ConfirmDialog d( m_container );
282
d.setText( tr( "The Last.fm Client is currently not the default player for Last.fm streams.\nDo you want to change that? *CHANEGCOPY*" ) );
283
d.setOperationString( "urlhandler" );
287
qDebug() << "Setting client as the default lastfm url-scheme handler worked:" << UnicornUtils::setPreferredAppForUrlScheme( QUrl( "lastfm://" ), MooseUtils::bundleDirPath() );
226
294
// Do we have a current user?
227
295
QString currentUser = The::settings().currentUsername();
407
480
ProxyTestRequest* proxyOnTest = new ProxyTestRequest( true );
408
481
ProxyTestRequest* proxyOffTest = new ProxyTestRequest( false );
410
connect( The::webService() , SIGNAL( proxyTestResult( bool ) ),
411
this, SLOT( onProxyTestComplete( bool ) ) );
483
disconnect( The::webService(), SIGNAL( proxyTestResult( bool, WebRequestResultCode ) ),
484
this, SLOT( onProxyTestComplete( bool, WebRequestResultCode ) ) );
485
connect( The::webService(), SIGNAL( proxyTestResult( bool, WebRequestResultCode ) ),
486
this, SLOT( onProxyTestComplete( bool, WebRequestResultCode ) ) );
413
488
proxyOnTest->start();
414
489
proxyOffTest->start();
419
494
emit event( Event::UserChanged, username );
420
onProxyTestComplete( false );
495
onProxyTestComplete( false, Request_Success );
423
emit event( Event::UserChanged, username );
498
m_proxyTestDone = false;
499
onProxyTestComplete( false, Request_Success );
428
LastFmApplication::onProxyTestComplete( bool proxySet )
505
LastFmApplication::onProxyTestComplete( bool proxySet, WebRequestResultCode result, bool authProxyTimerComplete )
430
507
// The only reason for this slot is to delay the handshake until we know whether
431
508
// to use the proxy or not.
509
qDebug() << "*********** ProxyTest Complete Result code = " << result;
512
if( result == Request_ProxyAuthenticationRequired && !authProxyTimerComplete )
514
// Proxy authentication required.
515
// Wait 3 seconds to see if a direct connection is available before
516
// prompting the user for login details.
517
QTimer::singleShot( 3000, this, SLOT( onAuthenticatedProxyTimerTimeout() ) );
433
521
if ( m_proxyTestDone ) return;
434
522
m_proxyTestDone = true;
436
525
LOGL( 3, ( proxySet ? "" : "not " ) << "using autodetected proxy settings" );
438
528
// HACK: since we're in a different function, we need to set the username again
439
529
// as it was a parameter to setUser.
440
530
QString username = m_user->settings().username();
442
532
emit event( Event::UserChanged, username );
445
534
QString password = m_user->settings().password();
446
535
QString version = The::settings().version();
570
////////////////////////////////////////////////////////////////////////////
571
// After a timeout period this will determine if a direct connection has //
572
// been established by another proxyTestRequest. If not then //
573
// authentication required dialog is shown before resuming user handshake.//
574
////////////////////////////////////////////////////////////////////////////
576
LastFmApplication::onAuthenticatedProxyTimerTimeout()
578
if( !m_proxyTestDone )
580
LastMessageBox::information( tr( "Proxy Authentication Required" ),
581
tr( "The proxy autodetection has detected a proxy server but does not have enough information to authenticate.\n\n"
582
"Please set the proxy settings to manual and enter the username and password required." ) );
584
The::container().showSettingsDialog( 3 );
585
onProxyTestComplete( true, Request_ProxyAuthenticationRequired, true );
591
LastFmApplication::loadExtensions()
593
foreach( QString path, Moose::extensionPaths() )
595
LOGL( 3, "Loading extension: " << path );
596
QObject* plugin = QPluginLoader( path ).instance();
599
LOGL( 1, "Failed to load " << path );
603
LOGL( 3, "Extension loaded" );
604
ExtensionInterface* iExtension = qobject_cast<ExtensionInterface *>( plugin );
607
QSettings* us = new CurrentUserSettings( this );
608
iExtension->setSettings( us );
482
616
LastFmApplication::winEventFilter( MSG * msg, long * result )
602
731
if ( code == Scrobbler::Handshaken )
604
733
QString const username = data.toString();
605
ScrobbleCache cache = ScrobbleCache::mediaDeviceCache( username );
606
// we show a nothing to scrobble message if empty usually
607
// but this is an automated check, and not user-triggered
608
if ( !cache.tracks().isEmpty() )
609
scrobbleMediaDeviceCache( username );
615
LastFmApplication::scrobbleMediaDeviceCache( const QString& username )
617
ScrobbleCache icache = ScrobbleCache::mediaDeviceCache( username );
618
ScrobbleCache cache( username );
620
if ( icache.tracks().isEmpty() )
622
//FIXME messages that interupt suck
623
LastMessageBox::information( tr( "Nothing To Scrobble" ),
624
tr( "Your iPod has nothing new to scrobble." ) );
628
MediaDeviceConfirmDialog dialog( username, m_container );
629
if ( dialog.exec() == QDialog::Accepted )
631
// copy tracks playCount() times so we submit the correct number of plays
632
QList<TrackInfo> tracks;
633
foreach ( TrackInfo const t, dialog.tracks() )
635
for ( int y = 0; y < t.playCount(); ++y )
638
// will add each track (but not each single play!) to the recently listened tracks
639
emit event( Event::MediaDeviceTrackScrobbled, QVariant::fromValue( t ) );
642
cache.append( tracks );
645
//HACK since our automatic sync iPod handling blows, we monitor all
646
// iTunes plays and check we aren't submitting regular iTunes plays
647
QFile f( icache.path() );
648
f.open( QFile::Text | QFile::ReadOnly );
650
xml.setContent( &f );
651
QString const databaseLastModified = xml.documentElement().attribute( "lastItunesUpdate" );
654
// will be empty if not an iPod mediadevice cache
655
if ( !databaseLastModified.isEmpty() )
657
ItunesScrobbleHistory().prune( databaseLastModified.toUInt() );
660
//FIXME possible race condition since LastFmHelper also writes to this file
661
QFile::remove( icache.path() );
663
m_scrobbler->scrobble( cache );
734
IPodScrobbler( username, m_container ).exec();
668
740
LastFmApplication::onRadioStateChanged( RadioState newState )
672
742
switch ( newState )
674
744
case State_Handshaken:
992
1051
if ( request.contains( "container://show" ) )
994
LOGL( 3, "Calling restoreWindow" );
995
1053
m_container->restoreWindow();
998
if ( request.contains( "container://checkScrobblerCache" ) )
1000
QString const username = request.split( "checkScrobblerCache/" ).value( 1 );
1002
if (m_scrobbler->canScrobble( username ))
1004
scrobbleMediaDeviceCache( username );
1007
Scrobbler::Init init;
1008
init.username = username;
1009
init.password = The::settings().user( username ).password();
1010
init.client_version = The::settings().version();
1012
m_scrobbler->handshake( init );
1014
// we will submit the mediadevice cache once the scrobbler comes back to us
1018
if ( request.contains( "container://addMediaDevice" ) )
1020
LOGL( 3, "Calling container for media device addition" );
1021
addMediaDevice( request.split( "addMediaDevice/" ).at( 1 ) );
1026
/** send to the already running instance of this Application */
1028
LastFmApplication::sendToInstance( const QString& data ) //static
1030
LOGL( 3, "sendToInstance (new instance): " << data );
1033
socket.connectToHost( QHostAddress::LocalHost, The::settings().controlPort() );
1035
if ( socket.waitForConnected( 500 ) )
1037
if ( data.length() > 0 )
1039
QByteArray utf8Data = data.toUtf8();
1040
socket.write( utf8Data, utf8Data.length() );
1049
LOGL( 1, "sendToInstance failed" << data );
1057
LastFmApplication::addMediaDevice( const QString& uid )
1059
ConfigWizard cw( NULL, ConfigWizard::MediaDevice, uid );
1060
if ( !cw.isWizardRunning() )
1057
if ( request.contains( "container://SubmitScrobbleCache/Device" ) )
1059
QStringList const params = request.split( "container://SubmitScrobbleCache/Device/" ).value( 1 ).split( "/" );
1060
int const vid = params.value( 2 ).toInt(); //vendor id
1061
int const pid = params.value( 3 ).toInt(); //product id
1062
QString const uid = params.value( 0 ) + '/' + params.value( 1 );
1063
QString username = The::settings().usernameForDeviceId( uid );
1065
if ( username.isEmpty() )
1067
username = The::currentUsername();
1068
The::settings().addMediaDevice( uid, username );
1071
if ( The::currentUsername() != username )
1073
LastMessageBox::warning( tr("iPod Scrobbling"),
1074
tr("<p>This iPod is associated with a different Last.fm account."
1075
"<p>Please log in as <b>%1</b> to submit the scrobbles."
1076
"<p>You can change the user association in the Options dialog.").arg( username ),
1082
IPodScrobbler( username, m_container ).exec();
1086
if ( request.startsWith( "container://Notification" ) && !ConfigWizard::isActive() )
1088
QString title, message;
1089
QStringList keys = request.mid( 12 ).split( '/' );
1091
if (keys.value( 1 ) == "Twiddly")
1093
title = tr("iPod Scrobbling");
1094
QString key = keys.value( 2 );
1096
// ask Toby if you wonder why we don't show any of these messages
1098
if (The::currentUser().giveMoreIPodScrobblingFeedback())
1100
if ( key == "Started" )
1101
message = tr( "Your iPod scrobbles are being determined. Please don't exit iTunes." );
1103
if ( key == "Error" )
1105
title = tr( "iPod Scrobbling Error" );
1106
message = keys.value( 3 ).replace( '_', ' ' );
1109
if ( key == "Finished" )
1111
int const n = keys.value( 3 ).toInt();
1113
message = tr( "Last.fm found %n scrobbles on your iPod.", "", n );
1115
message = tr( "No scrobbles were found on your iPod." );
1119
if (key == "Bootstrap")
1121
key = keys.value( 3 );
1122
if (key == "Started")
1123
message = tr("Preparing for iPod scrobbling, please don't exit iTunes or sync your iPod.");
1124
if (key == "Finished")
1125
message = tr("Last.fm is now ready for iPod scrobbling.");
1130
QString key = keys.value( 2 );
1132
if ( key == "IPodDetected" )
1134
title = tr( "iPod detected" );
1135
message = tr( "Your iPod will be scrobbled to your Last.fm profile from now on." );
1139
if (!message.isEmpty())
1141
m_container->showNotification( title, message );
1066
1149
LastFmApplication::onBootstrapReady( QString userName, QString pluginId )
1068
1152
// Ignore the bootstrap if we're not currently logged in as the user who initiated it.
1069
1153
// (The bootstrap file will be detected when the user who initiated it logs back in.)
1070
if( userName != The::currentUsername() ) return;
1154
if ( userName != The::currentUsername() )
1156
LastMessageBox::warning( "Bootstrap detected",
1157
QString( "Bootstrap information was detected for user %1.\n"
1158
"Please log in as %1 to submit this." ).arg( userName ),
1072
1164
PluginBootstrapper* bootstrapper = new PluginBootstrapper( pluginId, this );
1074
1165
bootstrapper->submitBootstrap();