1
0
mirror of https://github.com/tomahawk-player/tomahawk.git synced 2025-09-06 20:20:41 +02:00

Compare commits

...

27 Commits

Author SHA1 Message Date
Stefan Derkits
8afb12a340 added Acoustic & Electric songtype 2013-06-25 16:38:09 +02:00
Jeff Mitchell
d2fc754f7b Logging cleanup 2013-05-30 15:22:14 -04:00
Jeff Mitchell
84e827e9c7 Attempt working around sip issues 2013-05-30 14:54:22 -04:00
Jeff Mitchell
14b7b7c26e Merge remote-tracking branch 'origin/master' into hatchet 2013-05-30 10:33:52 -04:00
Jeff Mitchell
51dc3252af Use hostinfo pairs for registration 2013-05-30 10:33:24 -04:00
Jeff Mitchell
e16d49fd70 Merge remote-tracking branch 'origin/master' into hatchet
Conflicts:
	src/libtomahawk/Source.h
2013-05-29 10:13:22 -04:00
Jeff Mitchell
93ea5ae29c Reorder destructor 2013-05-28 13:17:39 -04:00
Jeff Mitchell
e22ad2c3d9 Use new get_connection method with our own uri_ptr to avoid the regex path that is not implemented in GCC 2013-05-28 13:15:32 -04:00
Jeff Mitchell
b3241f6a92 Merge remote-tracking branch 'origin/master' into hatchet 2013-05-28 10:01:36 -04:00
Jeff Mitchell
a6eb0b6b52 Update to use new tokens workflow 2013-05-24 16:10:47 -04:00
Jeff Mitchell
b4ca5b1bf1 Merge remote-tracking branch 'origin/master' into hatchet 2013-05-24 15:16:31 -04:00
Jeff Mitchell
b7bdb73492 Merge remote-tracking branch 'origin/master' into hatchet 2013-05-24 09:53:36 -04:00
Jeff Mitchell
8ab19b4922 Auto-reconnect to dreamcatcher on server disconnect 2013-05-23 22:39:24 -04:00
Jeff Mitchell
0dc2507c5a Use removeOne, not removeAll, as there won't be more than one 2013-05-23 21:15:59 -04:00
Jeff Mitchell
57bd89de24 Merge remote-tracking branch 'origin/master' into hatchet 2013-05-23 21:14:55 -04:00
Jeff Mitchell
8111235136 Fix spelling 2013-05-23 21:05:59 -04:00
Jeff Mitchell
373285a607 Correctly connect after authing 2013-05-23 21:01:50 -04:00
Jeff Mitchell
dfd686e049 Don't turn off sources if the disconnection was not due to the account
being disabled. This will allow for auto-reconnect to be possible.
2013-05-23 21:01:13 -04:00
Jeff Mitchell
1d5172b445 Remove registration from within Tomahawk as it's not supported. Use
nonce and check values. Display meaningful error messages when
authentication fails. Update URL for password logins. Add OTP field for
those with enabled accounts.
2013-05-23 19:15:51 -04:00
Jeff Mitchell
61b730e5a7 Add Mandella public key 2013-05-22 14:16:29 -04:00
Jeff Mitchell
16a4e198d7 Merge remote-tracking branch 'origin/master' into hatchet 2013-05-22 14:15:39 -04:00
Jeff Mitchell
519cb63ee5 Scale the pixmap appropriately 2013-05-21 13:04:36 -04:00
Jeff Mitchell
4348142890 Fix crash when authing 2013-05-21 12:51:37 -04:00
Jeff Mitchell
e1ea2feefd Improve connection handling and close behavior 2013-05-21 12:16:07 -04:00
Jeff Mitchell
a298f60b74 Merge remote-tracking branch 'origin/master' into hatchet 2013-05-20 22:45:40 -04:00
Jason Herskowitz
33e69c7696 Make hatchet logo transparent around the outside 2013-05-20 20:57:28 -04:00
Jeff Mitchell
4248c9db1a Import Hatchet plugin 2013-05-20 20:27:03 -04:00
29 changed files with 2486 additions and 14 deletions

View File

@@ -37,6 +37,7 @@ add_definitions( "-DQT_STRICT_ITERATORS" )
option(BUILD_GUI "Build Tomahawk with GUI" ON)
option(BUILD_RELEASE "Generate TOMAHAWK_VERSION without GIT info" OFF)
option(BUILD_TESTS "Build Tomahawk with unit tests" ON)
option(BUILD_HATCHET "Build the Hatchet plugin" OFF)
option(WITH_BREAKPAD "Build with breakpad integration" ON)
option(WITH_CRASHREPORTER "Build with CrashReporter" ON)

View File

@@ -3,16 +3,17 @@ include( ${CMAKE_CURRENT_LIST_DIR}/../../TomahawkAddPlugin.cmake )
file(GLOB SUBDIRECTORIES RELATIVE ${CMAKE_CURRENT_SOURCE_DIR} "*")
foreach(SUBDIRECTORY ${SUBDIRECTORIES})
if(IS_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/${SUBDIRECTORY}" AND EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/${SUBDIRECTORY}/CMakeLists.txt")
if(SUBDIRECTORY STREQUAL "xmpp")
if(SUBDIRECTORY STREQUAL "twitter")
elseif(SUBDIRECTORY STREQUAL "xmpp")
if( JREEN_FOUND )
add_subdirectory( xmpp )
endif()
elseif(SUBDIRECTORY STREQUAL "twitter")
if(QTWEETLIB_FOUND AND BUILD_GUI)
add_subdirectory( twitter )
elseif(SUBDIRECTORY STREQUAL "hatchet")
if(BUILD_HATCHET AND BUILD_GUI)
add_subdirectory(hatchet)
endif()
else()
add_subdirectory( ${SUBDIRECTORY} )
add_subdirectory(${SUBDIRECTORY})
endif()
endif()
endforeach()

23
src/accounts/hatchet/.gitignore vendored Normal file
View File

@@ -0,0 +1,23 @@
*-build/*
build/*
.directory
*.a
*.o
._*
*.user
*.swp
*.swo
Makefile*
moc_*
*~
*.sublime-project
*.sublime-workspace
/tomahawk
.kdev4
*.kdev4
*.kate-swp
clang/
win/
gcc/
tags
.DS_Store

View File

@@ -0,0 +1,52 @@
cmake_minimum_required(VERSION 2.8)
CMAKE_POLICY(SET CMP0017 NEW)
list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_LIST_DIR}/cmake/Modules")
if(NOT TOMAHAWK_LIBRARIES)
message(STATUS "BUILDING OUTSIDE TOMAHAWK")
find_package(Tomahawk REQUIRED)
else()
message(STATUS "BUILDING INSIDE TOMAHAWK")
set(TOMAHAWK_USE_FILE "${CMAKE_SOURCE_DIR}/TomahawkUse.cmake")
endif()
include( ${TOMAHAWK_USE_FILE} )
find_package(OpenSSL REQUIRED)
find_package(QCA2 REQUIRED)
find_package(websocketpp 0.2.99 REQUIRED)
include_directories(
${CMAKE_CURRENT_SOURCE_DIR}
${WEBSOCKETPP_INCLUDE_DIR}
${TOMAHAWK_INCLUDE_DIRS}
${QCA2_INCLUDE_DIR}
)
add_definitions(-D_WEBSOCKETPP_CPP11_STL_)
if(APPLE)
# http://stackoverflow.com/questions/7226753/osx-lion-xcode-4-1-how-do-i-setup-a-c0x-project/7236451#7236451
add_definitions(-std=c++11 -stdlib=libc++ -U__STRICT_ANSI__)
set(PLATFORM_SPECIFIC_LINK_LIBRARIES "/usr/lib/libc++.dylib")
else()
add_definitions(-std=c++0x)
endif()
tomahawk_add_plugin(hatchet
TYPE account
EXPORT_MACRO ACCOUNTDLLEXPORT_PRO
SOURCES
account/HatchetAccount.cpp
account/HatchetAccountConfig.cpp
sip/WebSocket.cpp
sip/WebSocketThreadController.cpp
sip/HatchetSip.cpp
UI
account/HatchetAccountConfig.ui
LINK_LIBRARIES
${TOMAHAWK_LIBRARIES}
${QCA2_LIBRARIES}
${PLATFORM_SPECIFIC_LINK_LIBRARIES}
)

View File

@@ -0,0 +1,417 @@
/* === This file is part of Tomahawk Player - <http://tomahawk-player.org> ===
*
* Copyright 2012 Leo Franchi <lfranchi@kde.org>
*
* Tomahawk is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Tomahawk is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Tomahawk. If not, see <http://www.gnu.org/licenses/>.
*/
#include "HatchetAccount.h"
#include "HatchetAccountConfig.h"
#include "utils/Closure.h"
#include "utils/Logger.h"
#include "sip/HatchetSip.h"
#include "utils/TomahawkUtils.h"
#include <QtPlugin>
#include <QFile>
#include <QNetworkRequest>
#include <QNetworkAccessManager>
#include <QNetworkReply>
#include <QUrl>
#include <QUuid>
#include <qjson/parser.h>
#include <qjson/serializer.h>
using namespace Tomahawk;
using namespace Accounts;
static QPixmap* s_icon = 0;
HatchetAccount* HatchetAccount::s_instance = 0;
const QString c_loginServer("https://mandella.hatchet.is/v1");
const QString c_accessTokenServer("https://mandella.hatchet.is/v1");
HatchetAccountFactory::HatchetAccountFactory()
{
#ifndef ENABLE_HEADLESS
if ( s_icon == 0 )
s_icon = new QPixmap( ":/hatchet-account/hatchet-icon-512x512.png" );
#endif
}
HatchetAccountFactory::~HatchetAccountFactory()
{
}
QPixmap
HatchetAccountFactory::icon() const
{
return *s_icon;
}
Account*
HatchetAccountFactory::createAccount( const QString& pluginId )
{
return new HatchetAccount( pluginId.isEmpty() ? generateId( factoryId() ) : pluginId );
}
// Hatchet account
HatchetAccount::HatchetAccount( const QString& accountId )
: Account( accountId )
, m_publicKey( nullptr )
{
s_instance = this;
QFile pemFile( ":/hatchet-account/mandella.pem" );
pemFile.open( QIODevice::ReadOnly );
tDebug() << Q_FUNC_INFO << "certs/mandella.pem: " << pemFile.readAll();
pemFile.close();
pemFile.open( QIODevice::ReadOnly );
QCA::ConvertResult conversionResult;
QCA::PublicKey publicKey = QCA::PublicKey::fromPEM(pemFile.readAll(), &conversionResult);
if ( QCA::ConvertGood != conversionResult )
{
tLog() << Q_FUNC_INFO << "INVALID PUBKEY READ";
return;
}
m_publicKey = new QCA::PublicKey( publicKey );
}
HatchetAccount::~HatchetAccount()
{
}
HatchetAccount*
HatchetAccount::instance()
{
return s_instance;
}
AccountConfigWidget*
HatchetAccount::configurationWidget()
{
if ( m_configWidget.isNull() )
m_configWidget = QWeakPointer<HatchetAccountConfig>( new HatchetAccountConfig( this ) );
return m_configWidget.data();
}
void
HatchetAccount::authenticate()
{
if ( connectionState() == Connected )
return;
if ( !authToken().isEmpty() )
{
qDebug() << "Have saved credentials with auth token:" << authToken();
if ( sipPlugin() )
sipPlugin()->connectPlugin();
}
else if ( !username().isEmpty() )
{
// Need to re-prompt for password, since we don't save it!
}
}
void
HatchetAccount::deauthenticate()
{
if ( !m_tomahawkSipPlugin.isNull() )
sipPlugin()->disconnectPlugin();
emit deauthenticated();
}
void
HatchetAccount::setConnectionState( Account::ConnectionState connectionState )
{
m_state = connectionState;
emit connectionStateChanged( connectionState );
}
Account::ConnectionState
HatchetAccount::connectionState() const
{
return m_state;
}
SipPlugin*
HatchetAccount::sipPlugin()
{
if ( m_tomahawkSipPlugin.isNull() )
{
tLog() << Q_FUNC_INFO;
m_tomahawkSipPlugin = QWeakPointer< HatchetSipPlugin >( new HatchetSipPlugin( this ) );
connect( m_tomahawkSipPlugin.data(), SIGNAL( authUrlDiscovered( Tomahawk::Accounts::HatchetAccount::Service, QString ) ),
this, SLOT( authUrlDiscovered( Tomahawk::Accounts::HatchetAccount::Service, QString ) ) );
return m_tomahawkSipPlugin.data();
}
return m_tomahawkSipPlugin.data();
}
QPixmap
HatchetAccount::icon() const
{
return *s_icon;
}
bool
HatchetAccount::isAuthenticated() const
{
return credentials().contains( "authtoken" );
}
QString
HatchetAccount::username() const
{
return credentials().value( "username" ).toString();
}
QByteArray
HatchetAccount::authToken() const
{
return credentials().value( "authtoken" ).toByteArray();
}
uint
HatchetAccount::authTokenExpiration() const
{
bool ok;
return credentials().value( "expiration" ).toUInt( &ok );
}
void
HatchetAccount::loginWithPassword( const QString& username, const QString& password, const QString &otp )
{
if ( username.isEmpty() || password.isEmpty() || !m_publicKey )
{
tLog() << "No tomahawk account username or pw or public key, not logging in";
return;
}
m_uuid = QUuid::createUuid().toString();
QCA::SecureArray sa( m_uuid.toLatin1() );
QCA::SecureArray result = m_publicKey->encrypt( sa, QCA::EME_PKCS1_OAEP );
QVariantMap params;
params[ "password" ] = password;
params[ "username" ] = username;
if ( !otp.isEmpty() )
params[ "otp" ] = otp;
params[ "client" ] = "Tomahawk (" + QHostInfo::localHostName() + ")";
params[ "nonce" ] = QString( result.toByteArray().toBase64() );
QJson::Serializer s;
const QByteArray msgJson = s.serialize( params );
QNetworkRequest req( QUrl( c_loginServer + "/auth/credentials") );
req.setHeader( QNetworkRequest::ContentTypeHeader, "application/json; charset=utf-8" );
QNetworkReply* reply = TomahawkUtils::nam()->post( req, msgJson );
NewClosure( reply, SIGNAL( finished() ), this, SLOT( onPasswordLoginFinished( QNetworkReply*, const QString& ) ), reply, username );
}
void
HatchetAccount::fetchAccessTokens( const QString& type )
{
if ( username().isEmpty() || authToken().isEmpty() )
{
tLog() << "No authToken, not logging in";
return;
}
if ( authTokenExpiration() < ( QDateTime::currentMSecsSinceEpoch() / 1000 ) )
tLog() << "Auth token has expired, but may still be valid on the server";
tLog() << "Fetching access tokens";
QNetworkRequest req( QUrl( c_accessTokenServer + "/tokens/" + type + "?authtoken=" + authToken() ) );
QNetworkReply* reply = TomahawkUtils::nam()->get( req );
connect( reply, SIGNAL( finished() ), this, SLOT( onFetchAccessTokensFinished() ) );
}
void
HatchetAccount::onPasswordLoginFinished( QNetworkReply* reply, const QString& username )
{
Q_ASSERT( reply );
bool ok;
const QVariantMap resp = parseReply( reply, ok );
if ( !ok )
{
tLog() << Q_FUNC_INFO << "Error getting parsed reply from auth server";
emit authError( "An error occurred reading the reply from the server");
deauthenticate();
return;
}
if ( !resp.value( "error" ).toString().isEmpty() )
{
tLog() << Q_FUNC_INFO << "Auth server returned an error";
emit authError( resp.value( "error" ).toString() );
deauthenticate();
return;
}
const QString nonce = resp.value( "data" ).toMap().value( "nonce" ).toString();
if ( nonce != m_uuid )
{
tLog() << Q_FUNC_INFO << "Auth server nonce value does not match!";
emit authError( "The nonce value was incorrect. YOUR ACCOUNT MAY BE COMPROMISED." );
deauthenticate();
return;
}
const QByteArray authenticationToken = resp.value( "data" ).toMap().value( "token" ).toByteArray();
uint expiration = resp.value( "data" ).toMap().value( "expiration" ).toUInt( &ok );
QVariantHash creds = credentials();
creds[ "username" ] = username;
creds[ "authtoken" ] = authenticationToken;
creds[ "expiration" ] = expiration;
setCredentials( creds );
syncConfig();
if ( !authenticationToken.isEmpty() )
{
if ( sipPlugin() )
sipPlugin()->connectPlugin();
}
}
void
HatchetAccount::onFetchAccessTokensFinished()
{
tLog() << Q_FUNC_INFO;
QNetworkReply* reply = qobject_cast< QNetworkReply* >( sender() );
Q_ASSERT( reply );
bool ok;
const QVariantMap resp = parseReply( reply, ok );
if ( !ok || !resp.value( "error" ).toString().isEmpty() )
{
tLog() << Q_FUNC_INFO << "Auth server returned an error";
if ( ok )
emit authError( resp.value( "error" ).toString() );
deauthenticate();
return;
}
QVariantHash creds = credentials();
QStringList tokenTypesFound;
tDebug() << Q_FUNC_INFO << "resp: " << resp;
foreach( QVariant tokenVariant, resp[ "data" ].toMap()[ "tokens" ].toList() )
{
QVariantMap tokenMap = tokenVariant.toMap();
QString tokenTypeName = tokenMap[ "type" ].toString() + "tokens";
if ( !tokenTypesFound.contains( tokenTypeName ) )
{
creds[ tokenTypeName ] = QVariantList();
tokenTypesFound.append( tokenTypeName );
}
creds[ tokenTypeName ] = creds[ tokenTypeName ].toList() << tokenMap;
}
tDebug() << Q_FUNC_INFO << "Creds: " << creds;
setCredentials( creds );
syncConfig();
tLog() << Q_FUNC_INFO << "Access tokens fetched successfully";
emit accessTokensFetched();
}
QString
HatchetAccount::authUrlForService( const Service &service ) const
{
return m_extraAuthUrls.value( service, QString() );
}
void
HatchetAccount::authUrlDiscovered( Service service, const QString &authUrl )
{
m_extraAuthUrls[ service ] = authUrl;
}
QVariantMap
HatchetAccount::parseReply( QNetworkReply* reply, bool& okRet ) const
{
QVariantMap resp;
reply->deleteLater();
bool ok;
int statusCode = reply->attribute( QNetworkRequest::HttpStatusCodeAttribute ).toInt( &ok );
if ( reply->error() != QNetworkReply::NoError && statusCode != 400 && statusCode != 500 )
{
tLog() << Q_FUNC_INFO << "Network error in command:" << reply->error() << reply->errorString();
okRet = false;
return resp;
}
QJson::Parser p;
resp = p.parse( reply, &ok ).toMap();
if ( !ok )
{
tLog() << Q_FUNC_INFO << "Error parsing JSON from server";
okRet = false;
return resp;
}
if ( !resp.value( "error", "" ).toString().isEmpty() )
{
tLog() << "Error from tomahawk server response, or in parsing from json:" << resp.value( "error" ).toString() << resp;
}
tDebug() << Q_FUNC_INFO << "Got keys" << resp.keys();
tDebug() << Q_FUNC_INFO << "Got values" << resp.values();
okRet = true;
return resp;
}
Q_EXPORT_PLUGIN2( Tomahawk::Accounts::AccountFactory, Tomahawk::Accounts::HatchetAccountFactory )

View File

@@ -0,0 +1,132 @@
/* === This file is part of Tomahawk Player - <http://tomahawk-player.org> ===
*
* Copyright 2012 Leo Franchi <lfranchi@kde.org>
*
* Tomahawk is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Tomahawk is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Tomahawk. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef HATCHET_ACCOUNT_H
#define HATCHET_ACCOUNT_H
#include <accounts/Account.h>
#include <accounts/AccountDllMacro.h>
#include <QtCrypto>
class QNetworkReply;
class HatchetSipPlugin;
namespace Tomahawk
{
namespace Accounts
{
class HatchetAccountConfig;
class ACCOUNTDLLEXPORT HatchetAccountFactory : public AccountFactory
{
Q_OBJECT
Q_INTERFACES( Tomahawk::Accounts::AccountFactory )
public:
HatchetAccountFactory();
virtual ~HatchetAccountFactory();
virtual QString factoryId() const { return "hatchetaccount"; }
virtual QString prettyName() const { return "Hatchet"; }
virtual QString description() const { return tr( "Connect to your Hatchet account" ); }
virtual bool isUnique() const { return true; }
AccountTypes types() const { return AccountTypes( SipType ); };
// virtual bool allowUserCreation() const { return false; }
#ifndef ENABLE_HEADLESS
virtual QPixmap icon() const;
#endif
virtual Account* createAccount ( const QString& pluginId = QString() );
};
class ACCOUNTDLLEXPORT HatchetAccount : public Account
{
Q_OBJECT
public:
enum Service {
Facebook = 0
};
HatchetAccount( const QString &accountId );
virtual ~HatchetAccount();
static HatchetAccount* instance();
QPixmap icon() const;
void authenticate();
void deauthenticate();
bool isAuthenticated() const;
void setConnectionState( Account::ConnectionState connectionState );
ConnectionState connectionState() const;
virtual Tomahawk::InfoSystem::InfoPluginPtr infoPlugin() { return Tomahawk::InfoSystem::InfoPluginPtr(); }
SipPlugin* sipPlugin();
AccountConfigWidget* configurationWidget();
QWidget* aclWidget() { return 0; }
QString username() const;
void fetchAccessTokens( const QString& type = "dreamcatcher" );
QString authUrlForService( const Service& service ) const;
signals:
void authError( QString error );
void deauthenticated();
void accessTokensFetched();
private slots:
void onPasswordLoginFinished( QNetworkReply*, const QString& username );
void onFetchAccessTokensFinished();
void authUrlDiscovered( Tomahawk::Accounts::HatchetAccount::Service service, const QString& authUrl );
private:
QByteArray authToken() const;
uint authTokenExpiration() const;
void loginWithPassword( const QString& username, const QString& password, const QString &otp );
QVariantMap parseReply( QNetworkReply* reply, bool& ok ) const;
QWeakPointer<HatchetAccountConfig> m_configWidget;
Account::ConnectionState m_state;
QWeakPointer< HatchetSipPlugin > m_tomahawkSipPlugin;
QHash< Service, QString > m_extraAuthUrls;
static HatchetAccount* s_instance;
friend class HatchetAccountConfig;
QCA::PublicKey* m_publicKey;
QString m_uuid;
};
}
}
#endif

View File

@@ -0,0 +1,182 @@
/* === This file is part of Tomahawk Player - <http://tomahawk-player.org> ===
*
* Copyright 2012 Leo Franchi <lfranchi@kde.org>
*
* Tomahawk is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Tomahawk is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Tomahawk. If not, see <http://www.gnu.org/licenses/>.
*/
#include "HatchetAccountConfig.h"
#include "HatchetAccount.h"
#include "utils/TomahawkUtils.h"
#include "utils/Logger.h"
#include "ui_HatchetAccountConfig.h"
#include <QMessageBox>
using namespace Tomahawk;
using namespace Accounts;
namespace {
enum ButtonAction {
Login,
Register,
Logout
};
}
HatchetAccountConfig::HatchetAccountConfig( HatchetAccount* account )
: AccountConfigWidget( 0 )
, m_ui( new Ui::HatchetAccountConfig )
, m_account( account )
{
Q_ASSERT( m_account );
m_ui->setupUi( this );
m_ui->label->setPixmap( m_ui->label->pixmap()->scaled( QSize( 128, 127 ), Qt::KeepAspectRatio, Qt::SmoothTransformation ) );
m_ui->loginButton->setDefault( true );
connect( m_ui->loginButton, SIGNAL( clicked( bool ) ), this, SLOT( login() ) );
connect( m_ui->usernameEdit, SIGNAL( textChanged( QString ) ), this, SLOT( fieldsChanged() ) );
connect( m_ui->passwordEdit, SIGNAL( textChanged( QString ) ), this, SLOT( fieldsChanged() ) );
connect( m_ui->otpEdit, SIGNAL( textChanged( QString ) ), this, SLOT( fieldsChanged() ) );
connect( m_account, SIGNAL( authError( QString ) ), this, SLOT( authError( QString ) ) );
connect( m_account, SIGNAL( deauthenticated() ), this, SLOT( showLoggedOut() ) );
connect( m_account, SIGNAL( accessTokensFetched() ), this, SLOT( accountInfoUpdated() ) );
if ( !m_account->authToken().isEmpty() )
accountInfoUpdated();
else
{
m_ui->usernameEdit->setText( m_account->username() );
showLoggedOut();
}
}
HatchetAccountConfig::~HatchetAccountConfig()
{
}
void
HatchetAccountConfig::login()
{
const ButtonAction action = static_cast< ButtonAction>( m_ui->loginButton->property( "action" ).toInt() );
if ( action == Login )
{
// Log in mode
m_account->loginWithPassword( m_ui->usernameEdit->text(), m_ui->passwordEdit->text(), m_ui->otpEdit->text() );
}
else if ( action == Logout )
{
// TODO
m_ui->usernameEdit->clear();
m_ui->passwordEdit->clear();
m_ui->otpEdit->clear();
QVariantHash creds = m_account->credentials();
creds.clear();
m_account->setCredentials( creds );
m_account->sync();
m_account->deauthenticate();
}
}
void
HatchetAccountConfig::fieldsChanged()
{
const QString username = m_ui->usernameEdit->text();
const QString password = m_ui->passwordEdit->text();
const ButtonAction action = static_cast< ButtonAction>( m_ui->loginButton->property( "action" ).toInt() );
m_ui->loginButton->setEnabled( !username.isEmpty() && !password.isEmpty() && action == Login );
m_ui->errorLabel->clear();
if ( action == Login )
m_ui->loginButton->setText( tr( "Login" ) );
}
void
HatchetAccountConfig::showLoggedIn()
{
m_ui->usernameLabel->hide();
m_ui->usernameEdit->hide();
m_ui->otpLabel->hide();
m_ui->otpEdit->hide();
m_ui->passwordLabel->hide();
m_ui->passwordEdit->hide();
m_ui->loggedInLabel->setText( tr( "Logged in as: %1" ).arg( m_account->username() ) );
m_ui->loggedInLabel->show();
m_ui->errorLabel->clear();
m_ui->errorLabel->hide();
m_ui->loginButton->setText( "Log out" );
m_ui->loginButton->setProperty( "action", Logout );
m_ui->loginButton->setDefault( true );
}
void
HatchetAccountConfig::showLoggedOut()
{
m_ui->usernameLabel->show();
m_ui->usernameEdit->show();
m_ui->passwordLabel->show();
m_ui->passwordEdit->show();
m_ui->otpEdit->show();
m_ui->otpLabel->show();
m_ui->loggedInLabel->clear();
m_ui->loggedInLabel->hide();
m_ui->errorLabel->clear();
m_ui->loginButton->setText( "Login" );
m_ui->loginButton->setProperty( "action", Login );
m_ui->loginButton->setDefault( true );
}
void
HatchetAccountConfig::accountInfoUpdated()
{
showLoggedIn();
return;
}
void
HatchetAccountConfig::authError( const QString &error )
{
QMessageBox::critical( this, "An error was encountered logging in:", error );
}
void
HatchetAccountConfig::showEvent( QShowEvent *event )
{
AccountConfigWidget::showEvent( event );
m_ui->loginButton->setDefault( true );
}

View File

@@ -0,0 +1,69 @@
/* === This file is part of Tomahawk Player - <http://tomahawk-player.org> ===
*
* Copyright 2012 Leo Franchi <lfranchi@kde.org>
*
* Tomahawk is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Tomahawk is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Tomahawk. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef HATCHET_ACCOUNT_CONFIG_H
#define HATCHET_ACCOUNT_CONFIG_H
#include <accounts/AccountConfigWidget.h>
#include <QWidget>
#include <QVariantMap>
class QNetworkReply;
namespace Ui {
class HatchetAccountConfig;
}
namespace Tomahawk {
namespace Accounts {
class HatchetAccount;
class HatchetAccountConfig : public AccountConfigWidget
{
Q_OBJECT
public:
explicit HatchetAccountConfig( HatchetAccount* account );
virtual ~HatchetAccountConfig();
private slots:
void login();
void fieldsChanged();
void showLoggedIn();
void showLoggedOut();
void accountInfoUpdated();
void authError( const QString& error );
protected:
//virtual void changeEvent( QEvent* event );
virtual void showEvent( QShowEvent* event );
private:
Ui::HatchetAccountConfig* m_ui;
HatchetAccount* m_account;
};
}
}
#endif

View File

@@ -0,0 +1,150 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>HatchetAccountConfig</class>
<widget class="QWidget" name="HatchetAccountConfig">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>301</width>
<height>404</height>
</rect>
</property>
<property name="windowTitle">
<string>Form</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<widget class="QLabel" name="label">
<property name="text">
<string/>
</property>
<property name="pixmap">
<pixmap resource="../resources.qrc">:/hatchet-account/hatchet-icon-512x512.png</pixmap>
</property>
<property name="alignment">
<set>Qt::AlignCenter</set>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="instructionLabel">
<property name="text">
<string>Connect to your Hatchet account</string>
</property>
<property name="alignment">
<set>Qt::AlignCenter</set>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="loggedInLabel">
<property name="font">
<font>
<pointsize>10</pointsize>
<weight>75</weight>
<bold>true</bold>
</font>
</property>
<property name="text">
<string/>
</property>
<property name="alignment">
<set>Qt::AlignCenter</set>
</property>
</widget>
</item>
<item>
<layout class="QFormLayout" name="credentialsLayout">
<item row="3" column="0">
<widget class="QLabel" name="otpLabel">
<property name="text">
<string>One-time
Password</string>
</property>
</widget>
</item>
<item row="1" column="0">
<widget class="QLabel" name="usernameLabel">
<property name="text">
<string>Username</string>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QLineEdit" name="usernameEdit">
<property name="placeholderText">
<string>Hatchet username</string>
</property>
</widget>
</item>
<item row="2" column="0">
<widget class="QLabel" name="passwordLabel">
<property name="text">
<string>Password:</string>
</property>
</widget>
</item>
<item row="2" column="1">
<widget class="QLineEdit" name="passwordEdit">
<property name="text">
<string/>
</property>
<property name="echoMode">
<enum>QLineEdit::Password</enum>
</property>
<property name="placeholderText">
<string>Hatchet password</string>
</property>
</widget>
</item>
<item row="3" column="1">
<widget class="QLineEdit" name="otpEdit">
<property name="placeholderText">
<string>(Only if configured)</string>
</property>
</widget>
</item>
</layout>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout_3">
<item>
<spacer name="horizontalSpacer">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="QLabel" name="errorLabel">
<property name="text">
<string/>
</property>
<property name="wordWrap">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="loginButton">
<property name="text">
<string>Login</string>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</widget>
<resources>
<include location="../resources.qrc"/>
</resources>
<connections/>
</ui>

View File

@@ -0,0 +1,14 @@
-----BEGIN PUBLIC KEY-----
MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAyT6j9B1hiRHwV96BSZJp
vLnGS0p6h1fxJiVGOjpcct/pA1rfVW66LTC7seYT6mF15flccIn5H8srAvH2a57S
uCMHDrHkQIoNDoOf+Ersw4tYzVYv+5h8IN9apYr6iqfT7rn3Fzb5oEB+GqcDurX3
4aaDjm+Wq0QjJAZf0fOaKor0ASejkj6EywpaHLwj51DKtf6SYbDEf52vDOzsDDnx
2AzVeqzPNFnnau6/v7rCi081b33qOrbJBtJjSNAJcM3iVf465k8KP2VD8nX3wzwT
9H0TEp67UMm0lz2i6gSAyEcIR2FNdTjVa1ZHYF2tnOihFDu0H4U4/4mPswpNNdOp
mtc5WxxK5ouKqxD8ArSoclmZMybIBbL0lLRjo4VC/olfbd1RQ57L9ETtNIf0xxKU
aweUkfWyJfU5ueaSlBaGoKSGugqp50ql3Td0m2JnJmaopdSi12L3EdlaO0+/OL3f
0aXbtnsZXlIHoq7FBEYcLhCNbwEeceV5Cveb5Os8w2331jkgpcPoXQFOtIVyva2B
3RadI0aALIbX5Tt0/AcbLeq10BJH8Ny2RSrhF2cxkjXEwQ68OUbCv1U4Si+EL44E
tctg8zj9rB8SG+miE18cXaQEUxA/olOU/Axkm8dLLLTxqWsD3VQYDxBgBemaY1OT
O32yipB9ko1sLCx6ZaQQ56sCAwEAAQ==
-----END PUBLIC KEY-----

View File

@@ -0,0 +1,14 @@
-----BEGIN PUBLIC KEY-----
MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAwqJWocwqceJBfO1Bbfdr
ozqYW+3T5VlmpBILw1OREDuUjdCO8mYvgfFTq5/JTZo/grLmd+LqntU3Y3E/u5Ij
Y92hRbPVmsQGIBcbTXuaasFLwjF9HAHaDcTbD0hS1mg1ZeO8r+uVvGmtdGDjulo/
lCqo8PP2SEu7Bx1w2XQLQSiCnKVZ5Zk25JQDiiDzsBmdTPhOaihU3kyo32qTOovs
xXv9d6J/Uziv/DcuoIDKS1Uvn1h91QTn2wDdW6NcPgjHq/BD0o7cHeuI6d7XXZm3
XM2TGQLl489h3kT3j6Tn30K3vXobYQvRXH76xjvYuWHlFaT/eSyEx0W/dIoN1Ot/
88SEaR0hlgBhL1e9sLbQUXKhHooeWApWUDVtioQGRklcZZV3eP3YUUkvUmhfls8R
dgcI68cfZp0JFtbosHtJdzOcDwncKwJCUOP0XIx/5xn4wrAUJqjq08IcRG5s6GUn
lw/n8Tb/paG2Ip5XhzNYX8fSklj2C6/5jZP2jTnAnFVy314U3vFEqLe/kQxIPWen
pPlTqLSBq1J0HomxSUuizjSF4u1oW7TchCO+zV1EjOD3FEsBpWgkG9NIyqOtdUVo
QH4c5HsflspmSAnM3zuap9NHPjFo2Q1tqoqqA2arTzO5zb1d0IQxe193dZ4zm4vj
fb5OBuKW3xwYpJbezb2rHH8CAwEAAQ==
-----END PUBLIC KEY-----

View File

@@ -0,0 +1,44 @@
-----BEGIN CERTIFICATE-----
MIIHyTCCBbGgAwIBAgIBATANBgkqhkiG9w0BAQUFADB9MQswCQYDVQQGEwJJTDEW
MBQGA1UEChMNU3RhcnRDb20gTHRkLjErMCkGA1UECxMiU2VjdXJlIERpZ2l0YWwg
Q2VydGlmaWNhdGUgU2lnbmluZzEpMCcGA1UEAxMgU3RhcnRDb20gQ2VydGlmaWNh
dGlvbiBBdXRob3JpdHkwHhcNMDYwOTE3MTk0NjM2WhcNMzYwOTE3MTk0NjM2WjB9
MQswCQYDVQQGEwJJTDEWMBQGA1UEChMNU3RhcnRDb20gTHRkLjErMCkGA1UECxMi
U2VjdXJlIERpZ2l0YWwgQ2VydGlmaWNhdGUgU2lnbmluZzEpMCcGA1UEAxMgU3Rh
cnRDb20gQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwggIiMA0GCSqGSIb3DQEBAQUA
A4ICDwAwggIKAoICAQDBiNsJvGxGfHiflXu1M5DycmLWwTYgIiRezul38kMKogZk
pMyONvg45iPwbm2xPN1yo4UcodM9tDMr0y+v/uqwQVlntsQGfQqedIXWeUyAN3rf
OQVSWff0G0ZDpNKFhdLDcfN1YjS6LIp/Ho/u7TTQEceWzVI9ujPW3U3eCztKS5/C
Ji/6tRYccjV3yjxd5srhJosaNnZcAdt0FCX+7bWgiA/deMotHweXMAEtcnn6RtYT
Kqi5pquDSR3l8u/d5AGOGAqPY1MWhWKpDhk6zLVmpsJrdAfkK+F2PrRt2PZE4XNi
HzvEvqBTViVsUQn3qqvKv3b9bZvzndu/PWa8DFaqr5hIlTpL36dYUNk4dalb6kMM
Av+Z6+hsTXBbKWWc3apdzK8BMewM69KN6Oqce+Zu9ydmDBpI125C4z/eIT574Q1w
+2OqqGwaVLRcJXrJosmLFqa7LH4XXgVNWG4SHQHuEhANxjJ/GP/89PrNbpHoNkm+
Gkhpi8KWTRoSsmkXwQqQ1vp5Iki/untp+HDH+no32NgN0nZPV/+Qt+OR0t3vwmC3
Zzrd/qqc8NSLf3Iizsafl7b4r4qgEKjZ+xjGtrVcUjyJthkqcwEKDwOzEmDyei+B
26Nu/yYwl/WL3YlXtq09s68rxbd2AvCl1iuahhQqcvbjM4xdCUsT37uMdBNSSwID
AQABo4ICUjCCAk4wDAYDVR0TBAUwAwEB/zALBgNVHQ8EBAMCAa4wHQYDVR0OBBYE
FE4L7xqkQFulF2mHMMo0aEPQQa7yMGQGA1UdHwRdMFswLKAqoCiGJmh0dHA6Ly9j
ZXJ0LnN0YXJ0Y29tLm9yZy9zZnNjYS1jcmwuY3JsMCugKaAnhiVodHRwOi8vY3Js
LnN0YXJ0Y29tLm9yZy9zZnNjYS1jcmwuY3JsMIIBXQYDVR0gBIIBVDCCAVAwggFM
BgsrBgEEAYG1NwEBATCCATswLwYIKwYBBQUHAgEWI2h0dHA6Ly9jZXJ0LnN0YXJ0
Y29tLm9yZy9wb2xpY3kucGRmMDUGCCsGAQUFBwIBFilodHRwOi8vY2VydC5zdGFy
dGNvbS5vcmcvaW50ZXJtZWRpYXRlLnBkZjCB0AYIKwYBBQUHAgIwgcMwJxYgU3Rh
cnQgQ29tbWVyY2lhbCAoU3RhcnRDb20pIEx0ZC4wAwIBARqBl0xpbWl0ZWQgTGlh
YmlsaXR5LCByZWFkIHRoZSBzZWN0aW9uICpMZWdhbCBMaW1pdGF0aW9ucyogb2Yg
dGhlIFN0YXJ0Q29tIENlcnRpZmljYXRpb24gQXV0aG9yaXR5IFBvbGljeSBhdmFp
bGFibGUgYXQgaHR0cDovL2NlcnQuc3RhcnRjb20ub3JnL3BvbGljeS5wZGYwEQYJ
YIZIAYb4QgEBBAQDAgAHMDgGCWCGSAGG+EIBDQQrFilTdGFydENvbSBGcmVlIFNT
TCBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTANBgkqhkiG9w0BAQUFAAOCAgEAFmyZ
9GYMNPXQhV59CuzaEE44HF7fpiUFS5Eyweg78T3dRAlbB0mKKctmArexmvclmAk8
jhvh3TaHK0u7aNM5Zj2gJsfyOZEdUauCe37Vzlrk4gNXcGmXCPleWKYK34wGmkUW
FjgKXlf2Ysd6AgXmvB618p70qSmD+LIU424oh0TDkBreOKk8rENNZEXO3SipXPJz
ewT4F+irsfMuXGRuczE6Eri8sxHkfY+BUZo7jYn0TZNmezwD7dOaHZrzZVD1oNB1
ny+v8OqCQ5j4aZyJecRDjkZy42Q2Eq/3JR44iZB3fsNrarnDy0RLrHiQi+fHLB5L
EUTINFInzQpdn4XBidUaePKVEFMy3YCEZnXZtWgo+2EuvoSoOMCZEoalHmdkrQYu
L6lwhceWD3yJZfWOQ1QOq92lgDmUYMA0yZZwLKMS9R9Ie70cfmu3nZD0Ijuu+Pwq
yvqCUqDvr0tVk+vBtfAii6w0TiYiBKGHLHVKt+V9E9e4DGTANtLJL4YSjCMJwRuC
O3NJo2pXh5Tl1njFmUNj403gdy3hZZlyaQQaRwnmDwFWJPsfvw55qVguucQJAX6V
um0ABj6y6koQOdjQK/W/7HW/lwLFCRsI3FU34oH7N4RDYiDK51ZLZer+bMEkkySh
NOsF/5oirpt9P/FlUQqmMGqz9IgcgA38corog14=
-----END CERTIFICATE-----

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

View File

@@ -0,0 +1,37 @@
# - Try to find QCA2 (Qt Cryptography Architecture 2)
# Once done this will define
#
# QCA2_FOUND - system has QCA2
# QCA2_INCLUDE_DIR - the QCA2 include directory
# QCA2_LIBRARIES - the libraries needed to use QCA2
# QCA2_DEFINITIONS - Compiler switches required for using QCA2
#
# use pkg-config to get the directories and then use these values
# in the FIND_PATH() and FIND_LIBRARY() calls
# Copyright (c) 2006, Michael Larouche, <michael.larouche@kdemail.net>
#
# Redistribution and use is allowed according to the terms of the BSD license.
# For details see the accompanying COPYING-CMAKE-SCRIPTS file.
if (NOT WIN32)
find_package(PkgConfig)
pkg_check_modules(PC_QCA2 qca2)
set(QCA2_DEFINITIONS ${PC_QCA2_CFLAGS_OTHER})
endif (NOT WIN32)
find_library(QCA2_LIBRARIES
NAMES qca
HINTS ${PC_QCA2_LIBDIR} ${PC_QCA2_LIBRARY_DIRS}
)
find_path(QCA2_INCLUDE_DIR qca.h
HINTS ${PC_QCA2_INCLUDEDIR} ${PC_QCA2_INCLUDE_DIRS}
PATH_SUFFIXES QtCrypto
PATHS /usr/local/lib/qca.framework/Headers/
)
include(FindPackageHandleStandardArgs)
find_package_handle_standard_args(QCA2 DEFAULT_MSG QCA2_LIBRARIES QCA2_INCLUDE_DIR)
mark_as_advanced(QCA2_INCLUDE_DIR QCA2_LIBRARIES)

View File

@@ -0,0 +1,8 @@
<RCC>
<qresource prefix="/hatchet-account">
<file alias="hatchet-icon-512x512.png">admin/icons/hatchet-icon-512x512.png</file>
<file alias="mandella.pem">admin/certs/mandella.pem</file>
<file alias="dreamcatcher.pem">admin/certs/dreamcatcher.pem</file>
<file alias="startcomroot.pem">admin/certs/startcomroot.pem</file>
</qresource>
</RCC>

View File

@@ -0,0 +1,605 @@
/* === This file is part of Tomahawk Player - <http://tomahawk-player.org> ===
*
* Copyright 2012, Jeff Mitchell <jeff@tomahawk-player.org>
*
* Tomahawk is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Tomahawk is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Tomahawk. If not, see <http://www.gnu.org/licenses/>.
*/
#include "HatchetSip.h"
#include "account/HatchetAccount.h"
#include "WebSocketThreadController.h"
//#include "WebSocket.h"
#include <database/Database.h>
#include <database/DatabaseImpl.h>
#include <database/DatabaseCommand_LoadOps.h>
#include <network/ControlConnection.h>
#include <network/Servent.h>
#include <sip/PeerInfo.h>
#include <utils/Logger.h>
#include <SourceList.h>
#include <QFile>
#include <QUuid>
HatchetSipPlugin::HatchetSipPlugin( Tomahawk::Accounts::Account *account )
: SipPlugin( account )
, m_sipState( Closed )
, m_version( 0 )
, m_publicKey( nullptr )
, m_reconnectTimer( this )
{
tLog() << Q_FUNC_INFO;
connect( m_account, SIGNAL( accessTokensFetched() ), this, SLOT( connectWebSocket() ) );
connect( Servent::instance(), SIGNAL( dbSyncTriggered() ), this, SLOT( dbSyncTriggered() ));
QFile pemFile( ":/hatchet-account/dreamcatcher.pem" );
pemFile.open( QIODevice::ReadOnly );
tDebug() << Q_FUNC_INFO << "certs/dreamcatcher.pem: " << pemFile.readAll();
pemFile.close();
pemFile.open( QIODevice::ReadOnly );
QCA::ConvertResult conversionResult;
QCA::PublicKey publicKey = QCA::PublicKey::fromPEM(pemFile.readAll(), &conversionResult);
if ( QCA::ConvertGood != conversionResult )
{
tLog() << Q_FUNC_INFO << "INVALID PUBKEY READ";
return;
}
m_publicKey = new QCA::PublicKey( publicKey );
m_reconnectTimer.setInterval( 0 );
m_reconnectTimer.setSingleShot( true );
connect( &m_reconnectTimer, SIGNAL( timeout() ), SLOT( connectPlugin() ) );
}
HatchetSipPlugin::~HatchetSipPlugin()
{
if ( m_webSocketThreadController )
{
m_webSocketThreadController->quit();
m_webSocketThreadController->wait( 60000 );
delete m_webSocketThreadController.data();
}
m_sipState = Closed;
hatchetAccount()->setConnectionState( Tomahawk::Accounts::Account::Disconnected );
}
bool
HatchetSipPlugin::isValid() const
{
return m_account->enabled() && m_account->isAuthenticated() && m_publicKey;
}
Tomahawk::Accounts::HatchetAccount*
HatchetSipPlugin::hatchetAccount() const
{
return qobject_cast< Tomahawk::Accounts::HatchetAccount* >( m_account );
}
void
HatchetSipPlugin::connectPlugin()
{
tLog() << Q_FUNC_INFO;
if ( !m_account->isAuthenticated() )
{
tLog() << Q_FUNC_INFO << "Account not authenticated, not continuing";
//FIXME: Prompt user for password?
return;
}
hatchetAccount()->setConnectionState( Tomahawk::Accounts::Account::Connecting );
hatchetAccount()->fetchAccessTokens();
}
void
HatchetSipPlugin::disconnectPlugin()
{
tLog() << Q_FUNC_INFO;
if ( m_webSocketThreadController && m_webSocketThreadController->isRunning() )
emit disconnectWebSocket();
else
webSocketDisconnected();
}
///////////////////////////// Connection methods ////////////////////////////////////
void
HatchetSipPlugin::connectWebSocket()
{
tLog() << Q_FUNC_INFO;
if ( m_webSocketThreadController )
{
tLog() << Q_FUNC_INFO << "Already have a thread running, bailing";
return;
}
m_webSocketThreadController = QPointer< WebSocketThreadController >( new WebSocketThreadController( this ) );
if ( !m_webSocketThreadController )
{
tLog() << Q_FUNC_INFO << "Could not create a new thread, bailing";
disconnectPlugin();
return;
}
if ( !isValid() )
{
tLog() << Q_FUNC_INFO << "Invalid state, not continuing with connection";
return;
}
m_token.clear();
QVariantList tokensCreds = m_account->credentials()[ "dreamcatchertokens" ].toList();
//FIXME: Don't blindly pick the first one that matches? Most likely, cycle through if the first one fails
QVariantMap connectVals;
foreach ( QVariant credObj, tokensCreds )
{
QVariantMap creds = credObj.toMap();
if ( creds.contains( "type" ) && creds[ "type" ].toString() == "dreamcatcher" )
{
connectVals = creds;
m_token = creds[ "token" ].toString();
break;
}
}
QString url;
if ( !connectVals.isEmpty() )
{
QString port = connectVals[ "port" ].toString();
if ( port == "443" )
url = "wss://";
else
url = "ws://";
url += connectVals[ "host" ].toString() + ':' + connectVals[ "port" ].toString();
}
if ( url.isEmpty() || m_token.isEmpty() )
{
tLog() << Q_FUNC_INFO << "Unable to find a proper connection endpoint; bailing";
disconnectPlugin();
return;
}
else
tLog() << Q_FUNC_INFO << "Connecting to Dreamcatcher endpoint at: " << url;
m_webSocketThreadController->setUrl( url );
m_webSocketThreadController->start();
}
void
HatchetSipPlugin::webSocketConnected()
{
tLog() << Q_FUNC_INFO << "WebSocket connected";
if ( m_token.isEmpty() || !m_account->credentials().contains( "username" ) )
{
tLog() << Q_FUNC_INFO << "access token or username is empty, aborting";
disconnectPlugin();
return;
}
hatchetAccount()->setConnectionState( Tomahawk::Accounts::Account::Connected );
m_sipState = AcquiringVersion;
m_uuid = QUuid::createUuid().toString();
QCA::SecureArray sa( m_uuid.toLatin1() );
QCA::SecureArray result = m_publicKey->encrypt( sa, QCA::EME_PKCS1_OAEP );
tDebug() << Q_FUNC_INFO << "uuid:" << m_uuid << ", size of uuid:" << m_uuid.size() << ", size of sa:" << sa.size() << ", size of result:" << result.size();
QVariantMap nonceVerMap;
nonceVerMap[ "version" ] = VERSION;
nonceVerMap[ "nonce" ] = QString( result.toByteArray().toBase64() );
sendBytes( nonceVerMap );
}
void
HatchetSipPlugin::webSocketDisconnected()
{
tLog() << Q_FUNC_INFO << "WebSocket disconnected";
m_reconnectTimer.stop();
if ( m_webSocketThreadController )
{
m_webSocketThreadController->quit();
m_webSocketThreadController->wait( 60000 );
delete m_webSocketThreadController.data();
}
m_sipState = Closed;
m_version = 0;
hatchetAccount()->setConnectionState( Tomahawk::Accounts::Account::Disconnected );
if ( hatchetAccount()->enabled() )
{
// Work on the assumption that we were disconnected because Dreamcatcher shut down
// Reconnect after a time; use reasonable backoff + random
int interval = m_reconnectTimer.interval() <= 25000 ? m_reconnectTimer.interval() + 5000 : 30000;
interval += QCA::Random::randomInt() % 30;
m_reconnectTimer.setInterval( interval );
m_reconnectTimer.start();
}
}
bool
HatchetSipPlugin::sendBytes( const QVariantMap& jsonMap ) const
{
tLog() << Q_FUNC_INFO;
if ( m_sipState == Closed )
{
tLog() << Q_FUNC_INFO << "was told to send bytes on a closed connection, not gonna do it";
return false;
}
QJson::Serializer serializer;
QByteArray bytes = serializer.serialize( jsonMap );
if ( bytes.isEmpty() )
{
tLog() << Q_FUNC_INFO << "could not serialize register structure to JSON";
return false;
}
tDebug() << Q_FUNC_INFO << "Sending bytes of size" << bytes.size();
emit rawBytes( bytes );
return true;
}
void
HatchetSipPlugin::messageReceived( const QByteArray &msg )
{
tDebug() << Q_FUNC_INFO << "WebSocket message: " << msg;
QJson::Parser parser;
bool ok;
QVariant jsonVariant = parser.parse( msg, &ok );
if ( !jsonVariant.isValid() )
{
tLog() << Q_FUNC_INFO << "Failed to parse message back from server";
return;
}
QVariantMap retMap = jsonVariant.toMap();
if ( m_sipState == AcquiringVersion )
{
tLog() << Q_FUNC_INFO << "In acquiring version state, expecting version/nonce information";
if ( !retMap.contains( "version" ) || !retMap.contains( "nonce" ) )
{
tLog() << Q_FUNC_INFO << "Failed to acquire version or nonce information";
disconnectPlugin();
return;
}
bool ok = false;
int ver = retMap[ "version" ].toInt( &ok );
if ( ver == 0 || !ok )
{
tLog() << Q_FUNC_INFO << "Failed to acquire version information";
disconnectPlugin();
return;
}
if ( retMap[ "nonce" ].toString() != m_uuid )
{
tLog() << Q_FUNC_INFO << "Failed to validate nonce";
disconnectPlugin();
return;
}
m_version = ver;
QVariantMap registerMap;
registerMap[ "command" ] = "register";
registerMap[ "token" ] = m_token;
registerMap[ "dbid" ] = Database::instance()->impl()->dbid();
registerMap[ "alias" ] = QHostInfo::localHostName();
QList< SipInfo > sipinfos = Servent::instance()->getLocalSipInfos( "default", "default" );
QVariantList hostinfos;
foreach ( SipInfo sipinfo, sipinfos )
{
QVariantMap hostinfo;
hostinfo[ "host" ] = sipinfo.host();
hostinfo[ "port" ] = sipinfo.port();
hostinfos << hostinfo;
}
registerMap[ "hostinfo" ] = hostinfos;
if ( !sendBytes( registerMap ) )
{
tLog() << Q_FUNC_INFO << "Failed sending message";
disconnectPlugin();
return;
}
m_sipState = Registering;
}
else if ( m_sipState == Registering )
{
tLog() << Q_FUNC_INFO << "In registering state, checking status of registration";
if ( retMap.contains( "status" ) &&
retMap[ "status" ].toString() == "success" )
{
tLog() << Q_FUNC_INFO << "Registered successfully";
m_sipState = Connected;
hatchetAccount()->setConnectionState( Tomahawk::Accounts::Account::Connected );
m_reconnectTimer.setInterval( 0 );
QTimer::singleShot(0, this, SLOT( dbSyncTriggered() ) );
return;
}
else
{
tLog() << Q_FUNC_INFO << "Failed to register successfully";
//m_ws.data()->stop();
return;
}
}
else if ( m_sipState != Connected )
{
// ...erm?
tLog() << Q_FUNC_INFO << "Got a message from a non connected socket?";
return;
}
else if ( !retMap.contains( "command" ) ||
!retMap[ "command" ].canConvert< QString >() )
{
tDebug() << Q_FUNC_INFO << "Unable to convert and/or interepret command from server";
return;
}
QString command = retMap[ "command" ].toString();
if ( command == "new-peer" )
newPeer( retMap );
else if ( command == "peer-authorization" )
peerAuthorization( retMap );
else if ( command == "synclastseen" )
sendOplog( retMap );
}
bool
HatchetSipPlugin::checkKeys( QStringList keys, const QVariantMap& map ) const
{
foreach ( QString key, keys )
{
if ( !map.contains( key ) )
{
tLog() << Q_FUNC_INFO << "Did not find the value" << key << "in the new-peer structure";
return false;
}
}
return true;
}
///////////////////////////// Peer handling methods ////////////////////////////////////
void
HatchetSipPlugin::newPeer( const QVariantMap& valMap )
{
const QString username = valMap[ "username" ].toString();
const QVariantList hostinfo = valMap[ "hostinfo" ].toList();
const QString dbid = valMap[ "dbid" ].toString();
tDebug() << Q_FUNC_INFO << "username:" << username << "dbid" << dbid;
QStringList keys( QStringList() << "command" << "username" << "hostinfo" << "dbid" );
if ( !checkKeys( keys, valMap ) )
return;
Tomahawk::peerinfo_ptr peerInfo = Tomahawk::PeerInfo::get( this, dbid, Tomahawk::PeerInfo::AutoCreate );
peerInfo->setContactId( username );
peerInfo->setFriendlyName( username );
QVariantMap data;
data.insert( "dbid", dbid );
peerInfo->setData( data );
QList< SipInfo > sipInfos;
foreach ( const QVariant listItem, hostinfo )
{
if ( !listItem.canConvert< QVariantMap >() )
continue;
QVariantMap hostpair = listItem.toMap();
if ( !hostpair.contains( "host" ) || !hostpair.contains( "port" ) )
continue;
const QString host = hostpair[ "host" ].toString();
unsigned int port = hostpair[ "port" ].toUInt();
if ( host.isEmpty() || port == 0 )
continue;
SipInfo sipInfo;
sipInfo.setNodeId( dbid );
sipInfo.setHost( host );
sipInfo.setPort( port );
sipInfo.setVisible( true );
sipInfos << sipInfo;
}
m_sipInfoHash[ dbid ] = sipInfos;
peerInfo->setStatus( Tomahawk::PeerInfo::Online );
}
void
HatchetSipPlugin::sendSipInfos(const Tomahawk::peerinfo_ptr& receiver, const QList< SipInfo >& infos)
{
if ( infos.size() == 0 )
{
tLog() << Q_FUNC_INFO << "Got no sipinfo data (list size 0)";
return;
}
const QString dbid = receiver->data().toMap().value( "dbid" ).toString();
tDebug() << Q_FUNC_INFO << "Send local info to " << receiver->friendlyName() << "(" << dbid << ") we are" << infos[ 0 ].nodeId() << "with offerkey " << infos[ 0 ].key();
QVariantMap sendMap;
sendMap[ "command" ] = "authorize-peer";
sendMap[ "dbid" ] = dbid;
sendMap[ "offerkey" ] = infos[ 0 ].key();
if ( !sendBytes( sendMap ) )
tLog() << Q_FUNC_INFO << "Failed sending message";
}
void
HatchetSipPlugin::peerAuthorization( const QVariantMap& valMap )
{
tDebug() << Q_FUNC_INFO << "dbid:" << valMap[ "dbid" ].toString() << "offerkey" << valMap[ "offerkey" ].toString();
QStringList keys( QStringList() << "command" << "dbid" << "offerkey" );
if ( !checkKeys( keys, valMap ) )
return;
QString dbid = valMap[ "dbid" ].toString();
Tomahawk::peerinfo_ptr peerInfo = Tomahawk::PeerInfo::get( this, dbid );
if( peerInfo.isNull() )
{
tLog() << Q_FUNC_INFO << "Received a peer-authorization for a peer we don't know about";
return;
}
QList< SipInfo > sipInfos = m_sipInfoHash[ dbid ];
for (int i = 0; i < sipInfos.size(); i++)
sipInfos[i].setKey( valMap[ "offerkey" ].toString() );
peerInfo->setSipInfos( sipInfos );
m_sipInfoHash.remove( dbid );
}
///////////////////////////// Syncing methods ////////////////////////////////////
void
HatchetSipPlugin::dbSyncTriggered()
{
if ( m_sipState != Connected )
return;
if ( !SourceList::instance() || SourceList::instance()->getLocal().isNull() )
return;
QVariantMap sourceMap;
sourceMap[ "command" ] = "synctrigger";
const Tomahawk::source_ptr src = SourceList::instance()->getLocal();
sourceMap[ "name" ] = src->friendlyName();
sourceMap[ "alias" ] = QHostInfo::localHostName();
sourceMap[ "friendlyname" ] = src->dbFriendlyName();
if ( !sendBytes( sourceMap ) )
{
tLog() << Q_FUNC_INFO << "Failed sending message";
return;
}
}
void
HatchetSipPlugin::sendOplog( const QVariantMap& valMap ) const
{
tDebug() << Q_FUNC_INFO;
DatabaseCommand_loadOps* cmd = new DatabaseCommand_loadOps( SourceList::instance()->getLocal(), valMap[ "lastrevision" ].toString() );
connect( cmd, SIGNAL( done( QString, QString, QList< dbop_ptr > ) ), SLOT( oplogFetched( QString, QString, QList< dbop_ptr > ) ) );
Database::instance()->enqueue( QSharedPointer< DatabaseCommand >( cmd ) );
}
void
HatchetSipPlugin::oplogFetched( const QString& sinceguid, const QString& /* lastguid */, const QList< dbop_ptr > ops )
{
tDebug() << Q_FUNC_INFO;
const uint_fast32_t byteMax = 1 << 25;
int currBytes = 0;
QVariantMap commandMap;
commandMap[ "command" ] = "oplog";
commandMap[ "startingrevision" ] = sinceguid;
currBytes += 60; //baseline for quotes, keys, commas, colons, etc.
QVariantList revisions;
tDebug() << Q_FUNC_INFO << "Found" << ops.size() << "ops";
foreach( const dbop_ptr op, ops )
{
currBytes += 80; //baseline for quotes, keys, commas, colons, etc.
QVariantMap revMap;
revMap[ "revision" ] = op->guid;
currBytes += op->guid.length();
revMap[ "singleton" ] = op->singleton;
currBytes += 5; //true or false
revMap[ "command" ] = op->command;
currBytes += op->command.length();
currBytes += 5; //true or false
if ( op->compressed )
{
revMap[ "compressed" ] = true;
QByteArray b64 = op->payload.toBase64();
revMap[ "payload" ] = op->payload.toBase64();
currBytes += b64.length();
}
else
{
revMap[ "compressed" ] = false;
revMap[ "payload" ] = op->payload;
currBytes += op->payload.length();
}
if ( currBytes >= (int)(byteMax - 1000000) ) // tack on an extra 1M for safety as it seems qjson puts in spaces
break;
else
revisions << revMap;
}
tDebug() << Q_FUNC_INFO << "Sending" << revisions.size() << "revisions";
commandMap[ "revisions" ] = revisions;
if ( !sendBytes( commandMap ) )
{
tLog() << Q_FUNC_INFO << "Failed sending message, attempting to send a blank message to clear sync state";
QVariantMap rescueMap;
rescueMap[ "command" ] = "oplog";
if ( !sendBytes( rescueMap ) )
{
tLog() << Q_FUNC_INFO << "Failed to send rescue map; state may be out-of-sync with server; reconnecting";
disconnectPlugin();
}
}
}

View File

@@ -0,0 +1,95 @@
/* === This file is part of Tomahawk Player - <http://tomahawk-player.org> ===
*
* Copyright 2012, Jeff Mitchell <jeff@tomahawk-player.org>
*
* Tomahawk is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Tomahawk is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Tomahawk. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef HATCHET_SIP_H
#define HATCHET_SIP_H
#include "accounts/AccountDllMacro.h"
#include "database/Op.h"
#include "sip/SipPlugin.h"
#include "account/HatchetAccount.h"
#include <QPointer>
#include <QTimer>
#include <QtCrypto>
class WebSocketThreadController;
const int VERSION = 1;
class ACCOUNTDLLEXPORT HatchetSipPlugin : public SipPlugin
{
Q_OBJECT
enum SipState {
AcquiringVersion,
Registering,
Connected,
Closed
};
public:
HatchetSipPlugin( Tomahawk::Accounts::Account *account );
virtual ~HatchetSipPlugin();
virtual bool isValid() const;
virtual void sendSipInfos( const Tomahawk::peerinfo_ptr& receiver, const QList< SipInfo >& infos );
public slots:
virtual void connectPlugin();
void disconnectPlugin();
void checkSettings() {}
void configurationChanged() {}
void addContact( const QString &, const QString& ) {}
void sendMsg( const QString&, const SipInfo& ) {}
void webSocketConnected();
void webSocketDisconnected();
signals:
void connectWebSocket() const;
void disconnectWebSocket() const;
void authUrlDiscovered( Tomahawk::Accounts::HatchetAccount::Service service, const QString& authUrl ) const;
void rawBytes( QByteArray bytes ) const;
private slots:
void dbSyncTriggered();
void messageReceived( const QByteArray& msg );
void connectWebSocket();
void oplogFetched( const QString& sinceguid, const QString& lastguid, const QList< dbop_ptr > ops );
private:
bool sendBytes( const QVariantMap& jsonMap ) const;
bool checkKeys( QStringList keys, const QVariantMap& map ) const;
void newPeer( const QVariantMap& valMap );
void peerAuthorization( const QVariantMap& valMap );
void sendOplog( const QVariantMap& valMap ) const;
Tomahawk::Accounts::HatchetAccount* hatchetAccount() const;
QPointer< WebSocketThreadController > m_webSocketThreadController;
QString m_token;
QString m_uuid;
SipState m_sipState;
int m_version;
QCA::PublicKey* m_publicKey;
QTimer m_reconnectTimer;
QHash< QString, QList< SipInfo > > m_sipInfoHash;
};
#endif

View File

@@ -0,0 +1,324 @@
/* === This file is part of Tomahawk Player - <http://tomahawk-player.org> ===
*
* Copyright 2012, Leo Franchi <lfranchi@kde.org>
*
* Tomahawk is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Tomahawk is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Tomahawk. If not, see <http://www.gnu.org/licenses/>.
*/
#include "WebSocket.h"
#include "utils/Logger.h"
#include <functional>
typedef typename websocketpp::lib::error_code error_code;
WebSocket::WebSocket( const QString& url )
: QObject( nullptr )
, m_disconnecting( false )
, m_url( url )
, m_outputStream()
, m_lastSocketState( QAbstractSocket::UnconnectedState )
, m_connectionTimer( this )
{
tLog() << Q_FUNC_INFO << "WebSocket constructing";
m_client = std::unique_ptr< hatchet_client >( new hatchet_client() );
m_client->set_message_handler( std::bind(&onMessage, this, std::placeholders::_1, std::placeholders::_2 ) );
m_client->set_close_handler( std::bind(&onClose, this, std::placeholders::_1 ) );
m_client->register_ostream( &m_outputStream );
m_connectionTimer.setSingleShot( true );
m_connectionTimer.setInterval( 30000 );
connect( &m_connectionTimer, SIGNAL( timeout() ), SLOT( disconnectWs() ) );
}
WebSocket::~WebSocket()
{
if ( m_connection )
m_connection.reset();
m_client.reset();
if ( m_socket )
delete m_socket.data();
}
void
WebSocket::setUrl( const QString &url )
{
tLog() << Q_FUNC_INFO << "Setting url to" << url;
if ( m_url == url )
return;
// We'll let automatic reconnection handle things
if ( m_socket && m_socket->isEncrypted() )
disconnectWs();
}
void
WebSocket::connectWs()
{
tLog() << Q_FUNC_INFO << "Connecting";
m_disconnecting = false;
if ( m_socket )
{
if ( m_socket->isEncrypted() )
return;
if ( m_socket->state() == QAbstractSocket::ClosingState )
QMetaObject::invokeMethod( this, "connectWs", Qt::QueuedConnection );
return;
}
tLog() << Q_FUNC_INFO << "Establishing new connection";
m_socket = QPointer< QSslSocket >( new QSslSocket( nullptr ) );
m_socket->addCaCertificate( QSslCertificate::fromPath( ":/hatchet-account/startcomroot.pem").first() );
QObject::connect( m_socket, SIGNAL( stateChanged( QAbstractSocket::SocketState ) ), SLOT( socketStateChanged( QAbstractSocket::SocketState ) ) );
QObject::connect( m_socket, SIGNAL( sslErrors( const QList< QSslError >& ) ), SLOT( sslErrors( const QList< QSslError >& ) ) );
QObject::connect( m_socket, SIGNAL( encrypted() ), SLOT( encrypted() ) );
QObject::connect( m_socket, SIGNAL( readyRead() ), SLOT( socketReadyRead() ) );
m_socket->connectToHostEncrypted( m_url.host(), m_url.port() );
m_connectionTimer.start();
}
void
WebSocket::disconnectWs( websocketpp::close::status::value status, const QString &reason )
{
tLog() << Q_FUNC_INFO << "Disconnecting";
m_disconnecting = true;
error_code ec;
if ( m_connection )
{
m_connection->close( status, reason.toAscii().constData(), ec );
QMetaObject::invokeMethod( this, "readOutput", Qt::QueuedConnection );
QTimer::singleShot( 5000, this, SLOT( disconnectSocket() ) ); //safety
return;
}
disconnectSocket();
}
void
WebSocket::disconnectSocket()
{
if ( m_socket && m_socket->state() == QAbstractSocket::ConnectedState )
m_socket->disconnectFromHost();
else
QMetaObject::invokeMethod( this, "cleanup", Qt::QueuedConnection );
QTimer::singleShot( 5000, this, SLOT( cleanup() ) ); //safety
}
void
WebSocket::cleanup()
{
tLog() << Q_FUNC_INFO << "Cleaning up";
m_outputStream.seekg( std::ios_base::end );
m_outputStream.seekp( std::ios_base::end );
m_queuedMessagesToSend.empty();
if ( m_connection )
m_connection.reset();
emit disconnected();
}
void
WebSocket::socketStateChanged( QAbstractSocket::SocketState state )
{
tLog() << Q_FUNC_INFO << "Socket state changed to" << state;
switch ( state )
{
case QAbstractSocket::ClosingState:
if ( m_lastSocketState == QAbstractSocket::ClosingState )
{
// It seems like it does not actually properly close, so force it
tLog() << Q_FUNC_INFO << "Got a double closing state, cleaning up and emitting disconnected";
m_socket->deleteLater();
m_lastSocketState = QAbstractSocket::UnconnectedState;
QMetaObject::invokeMethod( this, "cleanup", Qt::QueuedConnection );
return;
}
break;
case QAbstractSocket::UnconnectedState:
if ( m_lastSocketState == QAbstractSocket::UnconnectedState )
return;
tLog() << Q_FUNC_INFO << "Socket now unconnected, cleaning up and emitting disconnected";
m_socket->deleteLater();
m_lastSocketState = QAbstractSocket::UnconnectedState;
QMetaObject::invokeMethod( this, "cleanup", Qt::QueuedConnection );
return;
default:
;
}
m_lastSocketState = state;
}
void
WebSocket::sslErrors( const QList< QSslError >& errors )
{
tLog() << Q_FUNC_INFO << "Encountered errors when trying to connect via SSL";
foreach( QSslError error, errors )
tLog() << Q_FUNC_INFO << "Error: " << error.errorString();
QMetaObject::invokeMethod( this, "disconnectWs", Qt::QueuedConnection );
}
void
WebSocket::encrypted()
{
tLog() << Q_FUNC_INFO << "Encrypted connection to Dreamcatcher established";
error_code ec;
QUrl url(m_url);
websocketpp::uri_ptr uri(new websocketpp::uri(false, url.host().toStdString(), url.port(), "/"));
m_connection = m_client->get_connection( uri, ec );
if ( !m_connection || ec.value() != 0 )
{
tLog() << Q_FUNC_INFO << "Got error creating WS connection, error is:" << QString::fromStdString( ec.message() );
disconnectWs();
return;
}
m_client->connect( m_connection );
QMetaObject::invokeMethod( this, "readOutput", Qt::QueuedConnection );
emit connected();
}
void
WebSocket::readOutput()
{
if ( !m_connection )
return;
tDebug() << Q_FUNC_INFO;
std::string outputString = m_outputStream.str();
if ( outputString.size() > 0 )
{
m_outputStream.str("");
tDebug() << Q_FUNC_INFO << "Got string of size" << outputString.size() << "from ostream";
qint64 sizeWritten = m_socket->write( outputString.data(), outputString.size() );
tDebug() << Q_FUNC_INFO << "Wrote" << sizeWritten << "bytes to the socket";
if ( sizeWritten == -1 )
{
tLog() << Q_FUNC_INFO << "Error during writing, closing connection";
QMetaObject::invokeMethod( this, "disconnectWs", Qt::QueuedConnection );
return;
}
}
if ( m_queuedMessagesToSend.size() )
{
if ( m_connection->get_state() == websocketpp::session::state::open )
{
foreach( QByteArray message, m_queuedMessagesToSend )
{
tDebug() << Q_FUNC_INFO << "Sending queued message of size" << message.size();
m_connection->send( std::string( message.constData(), message.size() ), websocketpp::frame::opcode::TEXT );
}
m_queuedMessagesToSend.clear();
QMetaObject::invokeMethod( this, "readOutput", Qt::QueuedConnection );
m_connectionTimer.stop();
}
else if ( !m_disconnecting )
QTimer::singleShot( 200, this, SLOT( readOutput() ) );
}
else
m_connectionTimer.stop();
}
void
WebSocket::socketReadyRead()
{
tDebug() << Q_FUNC_INFO;
if ( !m_socket || !m_socket->isEncrypted() )
return;
if ( !m_socket->isValid() )
{
tLog() << Q_FUNC_INFO << "Socket appears to no longer be valid. Something is wrong; disconnecting";
QMetaObject::invokeMethod( this, "disconnectWs", Qt::QueuedConnection );
return;
}
if ( qint64 bytes = m_socket->bytesAvailable() )
{
tDebug() << Q_FUNC_INFO << "Bytes available:" << bytes;
QByteArray buf;
buf.resize( bytes );
qint64 bytesRead = m_socket->read( buf.data(), bytes );
tDebug() << Q_FUNC_INFO << "Bytes read:" << bytesRead; // << ", content is" << websocketpp::utility::to_hex( buf.constData(), bytesRead ).data();
if ( bytesRead != bytes )
{
tLog() << Q_FUNC_INFO << "Error occurred during socket read. Something is wrong; disconnecting";
QMetaObject::invokeMethod( this, "disconnectWs", Qt::QueuedConnection );
return;
}
std::stringstream ss( std::string( buf.constData(), bytesRead ) );
ss >> *m_connection;
}
QMetaObject::invokeMethod( this, "readOutput", Qt::QueuedConnection );
}
void
WebSocket::encodeMessage( const QByteArray &bytes )
{
tDebug() << Q_FUNC_INFO << "Encoding message"; //, message is" << bytes.constData();
if ( !m_connection )
{
tLog() << Q_FUNC_INFO << "Asked to send message but do not have a valid connection!";
return;
}
if ( m_connection->get_state() != websocketpp::session::state::open )
{
tLog() << Q_FUNC_INFO << "Connection not yet open/upgraded, queueing work to send";
m_queuedMessagesToSend.append( bytes );
m_connectionTimer.start();
}
else
m_connection->send( std::string( bytes.constData() ), websocketpp::frame::opcode::TEXT );
QMetaObject::invokeMethod( this, "readOutput", Qt::QueuedConnection );
}
void
onMessage( WebSocket* ws, websocketpp::connection_hdl, hatchet_client::message_ptr msg )
{
tDebug() << Q_FUNC_INFO << "Handling message";
std::string payload = msg->get_payload();
ws->decodedMessage( QByteArray( payload.data(), payload.length() ) );
}
void
onClose( WebSocket *ws, websocketpp::connection_hdl )
{
tDebug() << Q_FUNC_INFO << "Handling message";
QMetaObject::invokeMethod( ws, "disconnectSocket", Qt::QueuedConnection );
}

View File

@@ -0,0 +1,84 @@
/* === This file is part of Tomahawk Player - <http://tomahawk-player.org> ===
*
* Copyright 2012, Leo Franchi <lfranchi@kde.org>
*
* Tomahawk is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Tomahawk is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Tomahawk. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef WEBSOCKET__H
#define WEBSOCKET__H
#include "DllMacro.h"
#include "hatchet_config.hpp"
#include <websocketpp/client.hpp>
#include <QPointer>
#include <QSslSocket>
#include <QTimer>
#include <QUrl>
#include <memory>
typedef typename websocketpp::client< websocketpp::config::hatchet_client > hatchet_client;
class WebSocket;
void onMessage( WebSocket* ws, websocketpp::connection_hdl, hatchet_client::message_ptr msg );
void onClose( WebSocket* ws, websocketpp::connection_hdl );
class DLLEXPORT WebSocket : public QObject
{
Q_OBJECT
public:
explicit WebSocket( const QString& url );
virtual ~WebSocket();
signals:
void connected();
void disconnected();
void decodedMessage( QByteArray bytes );
public slots:
void setUrl( const QString& url );
void connectWs();
void disconnectWs( websocketpp::close::status::value status = websocketpp::close::status::normal, const QString& reason = QString( "Disconnecting" ) );
void encodeMessage( const QByteArray& bytes );
private slots:
void socketStateChanged( QAbstractSocket::SocketState state );
void sslErrors( const QList< QSslError >& errors );
void disconnectSocket();
void cleanup();
void encrypted();
void readOutput();
void socketReadyRead();
private:
Q_DISABLE_COPY( WebSocket )
friend void onMessage( WebSocket *ws, websocketpp::connection_hdl, hatchet_client::message_ptr msg );
friend void onClose( WebSocket *ws, websocketpp::connection_hdl );
bool m_disconnecting;
QUrl m_url;
std::stringstream m_outputStream;
std::unique_ptr< hatchet_client > m_client;
hatchet_client::connection_ptr m_connection;
QPointer< QSslSocket > m_socket;
QAbstractSocket::SocketState m_lastSocketState;
QList< QByteArray > m_queuedMessagesToSend;
QTimer m_connectionTimer;
};
#endif

View File

@@ -0,0 +1,71 @@
/* === This file is part of Tomahawk Player - <http://tomahawk-player.org> ===
*
* Copyright 2012, Leo Franchi <lfranchi@kde.org>
*
* Tomahawk is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Tomahawk is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Tomahawk. If not, see <http://www.gnu.org/licenses/>.
*/
#include "WebSocketThreadController.h"
#include "WebSocket.h"
#include "utils/Logger.h"
WebSocketThreadController::WebSocketThreadController( QObject* sip )
: QThread( nullptr )
, m_webSocket( nullptr )
, m_sip( sip )
{
}
WebSocketThreadController::~WebSocketThreadController()
{
if ( m_webSocket )
{
delete m_webSocket;
m_webSocket = 0;
}
}
void
WebSocketThreadController::setUrl( const QString &url )
{
m_url = url;
if ( m_webSocket )
{
QMetaObject::invokeMethod( m_webSocket, "setUrl", Qt::QueuedConnection, Q_ARG( QString, url ));
}
}
void
WebSocketThreadController::run()
{
tLog() << Q_FUNC_INFO << "Starting";
m_webSocket = QPointer< WebSocket >( new WebSocket( m_url ) );
if ( m_webSocket && m_sip )
{
tLog() << Q_FUNC_INFO << "Have a valid websocket and parent";
connect( m_sip, SIGNAL( connectWebSocket() ), m_webSocket, SLOT( connectWs() ), Qt::QueuedConnection );
connect( m_sip, SIGNAL( disconnectWebSocket() ), m_webSocket, SLOT( disconnectWs() ), Qt::QueuedConnection );
connect( m_sip, SIGNAL( rawBytes( QByteArray ) ), m_webSocket, SLOT( encodeMessage( QByteArray ) ), Qt::QueuedConnection );
connect( m_webSocket, SIGNAL( connected() ), m_sip, SLOT( webSocketConnected() ), Qt::QueuedConnection );
connect( m_webSocket, SIGNAL( disconnected() ), m_sip, SLOT( webSocketDisconnected() ), Qt::QueuedConnection );
connect( m_webSocket, SIGNAL( decodedMessage( QByteArray ) ), m_sip, SLOT( messageReceived( QByteArray ) ), Qt::QueuedConnection );
QMetaObject::invokeMethod( m_webSocket, "connectWs", Qt::QueuedConnection );
exec();
delete m_webSocket;
m_webSocket = 0;
}
}

View File

@@ -0,0 +1,49 @@
/* === This file is part of Tomahawk Player - <http://tomahawk-player.org> ===
*
* Copyright 2012, Leo Franchi <lfranchi@kde.org>
*
* Tomahawk is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Tomahawk is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Tomahawk. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef WEBSOCKET_THREAD_CONTROLLER_H
#define WEBSOCKET_THREAD_CONTROLLER_H
#include "DllMacro.h"
#include <QPointer>
#include <QThread>
class WebSocket;
class DLLEXPORT WebSocketThreadController : public QThread
{
Q_OBJECT
public:
explicit WebSocketThreadController( QObject* sip );
virtual ~WebSocketThreadController();
void setUrl( const QString &url );
protected:
void run();
private:
Q_DISABLE_COPY( WebSocketThreadController )
QPointer< WebSocket > m_webSocket;
QPointer< QObject > m_sip;
QString m_url;
};
#endif

View File

@@ -0,0 +1,72 @@
/*
* Copyright (c) 2013, Peter Thorson. All rights reserved.
* Copyright (c) 2013, Jeff Mitchell <jeff@tomahawk-player.org>. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* * Neither the name of the WebSocket++ Project nor the
* names of its contributors may be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL PETER THORSON BE LIABLE FOR ANY
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
*/
#ifndef WEBSOCKETPP_CONFIG_HATCHET_CLIENT_HPP
#define WEBSOCKETPP_CONFIG_HATCHET_CLIENT_HPP
#include <websocketpp/config/core_client.hpp>
#include <websocketpp/transport/iostream/endpoint.hpp>
#include <websocketpp/concurrency/none.hpp>
namespace websocketpp {
namespace config {
struct hatchet_client : public core_client {
typedef hatchet_client type;
typedef websocketpp::concurrency::none concurrency_type;
typedef core_client::request_type request_type;
typedef core_client::response_type response_type;
typedef core_client::message_type message_type;
typedef core_client::con_msg_manager_type con_msg_manager_type;
typedef core_client::endpoint_msg_manager_type endpoint_msg_manager_type;
typedef core_client::alog_type alog_type;
typedef core_client::elog_type elog_type;
typedef core_client::rng_type rng_type;
struct transport_config {
typedef type::concurrency_type concurrency_type;
typedef type::alog_type alog_type;
typedef type::elog_type elog_type;
typedef type::request_type request_type;
typedef type::response_type response_type;
};
typedef websocketpp::transport::iostream::endpoint<transport_config> transport_type;
//static const websocketpp::log::level alog_level = websocketpp::log::alevel::all;
};
} // namespace config
} // namespace websocketpp
#endif // WEBSOCKETPP_CONFIG_HATCHET_CLIENT_HPP

View File

@@ -70,8 +70,12 @@ Source::Source( int id, const QString& nodeId )
if ( m_isLocal )
{
connect( Accounts::AccountManager::instance(), SIGNAL( connected( Tomahawk::Accounts::Account* ) ), SLOT( setOnline() ) );
connect( Accounts::AccountManager::instance(), SIGNAL( disconnected( Tomahawk::Accounts::Account* ) ), SLOT( setOffline() ) );
connect( Accounts::AccountManager::instance(),
SIGNAL( connected( Tomahawk::Accounts::Account* ) ),
SLOT( setOnline() ) );
connect( Accounts::AccountManager::instance(),
SIGNAL( disconnected( Tomahawk::Accounts::Account*, Tomahawk::Accounts::AccountManager::DisconnectReason ) ),
SLOT( handleDisconnect( Tomahawk::Accounts::Account*, Tomahawk::Accounts::AccountManager::DisconnectReason ) ) );
}
}
@@ -330,6 +334,13 @@ Source::removeCollection( const collection_ptr& c )
emit collectionRemoved( c );
}
void
Source::handleDisconnect( Tomahawk::Accounts::Account*, Tomahawk::Accounts::AccountManager::DisconnectReason reason )
{
if ( reason == Tomahawk::Accounts::AccountManager::Disabled )
setOffline();
}
void
Source::setOffline()

View File

@@ -26,6 +26,7 @@
#include <QtCore/QVariantMap>
#include "Typedefs.h"
#include "accounts/AccountManager.h"
#include "network/ControlConnection.h"
#include "network/DbSyncConnection.h"
#include "collection/Collection.h"
@@ -136,6 +137,7 @@ private slots:
void dbLoaded( unsigned int id, const QString& fname );
void updateIndexWhenSynced();
void handleDisconnect( Tomahawk::Accounts::Account*, Tomahawk::Accounts::AccountManager::DisconnectReason reason );
void setOffline();
void setOnline();

View File

@@ -512,7 +512,8 @@ AccountManager::onStateChanged( Account::ConnectionState state )
if ( account->connectionState() == Account::Disconnected )
{
m_connectedAccounts.removeAll( account );
emit disconnected( account );
DisconnectReason reason = account->enabled() ? Disconnected : Disabled;
emit disconnected( account, reason );
}
else if ( account->connectionState() == Account::Connected )
{

View File

@@ -21,7 +21,8 @@
#ifndef ACCOUNTMANAGER_H
#define ACCOUNTMANAGER_H
#include <QtCore/QObject>
#include <QFlags>
#include <QObject>
#include "Typedefs.h"
#include "DllMacro.h"
@@ -42,6 +43,11 @@ class DLLEXPORT AccountManager : public QObject
Q_OBJECT
public:
enum DisconnectReason {
Disconnected,
Disabled
};
static AccountManager* instance();
explicit AccountManager( QObject *parent );
@@ -61,7 +67,7 @@ public:
void hookupAndEnable( Account* account, bool startup = false ); /// Hook up signals and start the plugin
void removeAccount( Account* account );
QList< Account* > accounts() const { return m_accounts; };
QList< Account* > accounts() const { return m_accounts; }
QList< Account* > accounts( Tomahawk::Accounts::AccountType type ) const { return m_accountsByAccountType[ type ]; }
QList< Account* > accountsFromFactory( Tomahawk::Accounts::AccountFactory* factory ) const;
@@ -105,7 +111,7 @@ signals:
void removed( Tomahawk::Accounts::Account* );
void connected( Tomahawk::Accounts::Account* );
void disconnected( Tomahawk::Accounts::Account* );
void disconnected( Tomahawk::Accounts::Account*, Tomahawk::Accounts::AccountManager::DisconnectReason );
void authError( Tomahawk::Accounts::Account* );
void stateChanged( Account* p, Accounts::Account::ConnectionState state );

View File

@@ -496,6 +496,8 @@ Tomahawk::EchonestControl::updateWidgets()
QComboBox* combo = new QComboBox();
combo->addItem( tr( "Studio" ), "studio" );
combo->addItem( tr( "Live" ), "live" );
combo->addItem( tr( "Acoustic" ), "acoustic" );
combo->addItem( tr( "Electric" ), "electric" );
combo->addItem( tr( "Christmas" ), "christmas" );
connect( match, SIGNAL( activated( int ) ), this, SLOT( updateData() ) );

View File

@@ -54,7 +54,7 @@ Closure::Closure(QObject* sender,
Closure::Closure(QObject* sender,
const char* signal,
std::tr1::function<void()> callback)
function<void()> callback)
: callback_(callback) {
Connect(sender, signal);
}

View File

@@ -21,7 +21,13 @@
#include "DllMacro.h"
#ifdef _WEBSOCKETPP_CPP11_STL_
#include <functional>
using std::function;
#else
#include <tr1/functional>
using std::tr1::function;
#endif
#include <QMetaMethod>
#include <QObject>
@@ -64,7 +70,7 @@ class DLLEXPORT Closure : public QObject, boost::noncopyable {
const ClosureArgumentWrapper* val3 = 0);
Closure(QObject* sender, const char* signal,
std::tr1::function<void()> callback);
function<void()> callback);
void setAutoDelete( bool autoDelete ) { autoDelete_ = autoDelete; }
@@ -87,7 +93,7 @@ class DLLEXPORT Closure : public QObject, boost::noncopyable {
void Connect(QObject* sender, const char* signal);
QMetaMethod slot_;
std::tr1::function<void()> callback_;
function<void()> callback_;
bool autoDelete_;
QObject* outOfThreadReceiver_;