1
0
mirror of https://github.com/tomahawk-player/tomahawk.git synced 2025-08-12 00:54:20 +02:00

Add hatchet plugin to master

This commit is contained in:
Jeff Mitchell
2013-05-30 15:29:04 -04:00
parent 7ba846aad3
commit 42880a3c97
27 changed files with 2461 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()

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

@@ -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_;