mirror of
https://github.com/tomahawk-player/tomahawk.git
synced 2025-08-18 03:41:27 +02:00
Add hatchet plugin to master
This commit is contained in:
@@ -37,6 +37,7 @@ add_definitions( "-DQT_STRICT_ITERATORS" )
|
|||||||
option(BUILD_GUI "Build Tomahawk with GUI" ON)
|
option(BUILD_GUI "Build Tomahawk with GUI" ON)
|
||||||
option(BUILD_RELEASE "Generate TOMAHAWK_VERSION without GIT info" OFF)
|
option(BUILD_RELEASE "Generate TOMAHAWK_VERSION without GIT info" OFF)
|
||||||
option(BUILD_TESTS "Build Tomahawk with unit tests" ON)
|
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_BREAKPAD "Build with breakpad integration" ON)
|
||||||
option(WITH_CRASHREPORTER "Build with CrashReporter" ON)
|
option(WITH_CRASHREPORTER "Build with CrashReporter" ON)
|
||||||
|
@@ -3,13 +3,14 @@ include( ${CMAKE_CURRENT_LIST_DIR}/../../TomahawkAddPlugin.cmake )
|
|||||||
file(GLOB SUBDIRECTORIES RELATIVE ${CMAKE_CURRENT_SOURCE_DIR} "*")
|
file(GLOB SUBDIRECTORIES RELATIVE ${CMAKE_CURRENT_SOURCE_DIR} "*")
|
||||||
foreach(SUBDIRECTORY ${SUBDIRECTORIES})
|
foreach(SUBDIRECTORY ${SUBDIRECTORIES})
|
||||||
if(IS_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/${SUBDIRECTORY}" AND EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/${SUBDIRECTORY}/CMakeLists.txt")
|
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 )
|
if( JREEN_FOUND )
|
||||||
add_subdirectory( xmpp )
|
add_subdirectory( xmpp )
|
||||||
endif()
|
endif()
|
||||||
elseif(SUBDIRECTORY STREQUAL "twitter")
|
elseif(SUBDIRECTORY STREQUAL "hatchet")
|
||||||
if(QTWEETLIB_FOUND AND BUILD_GUI)
|
if(BUILD_HATCHET AND BUILD_GUI)
|
||||||
add_subdirectory( twitter )
|
add_subdirectory(hatchet)
|
||||||
endif()
|
endif()
|
||||||
else()
|
else()
|
||||||
add_subdirectory(${SUBDIRECTORY})
|
add_subdirectory(${SUBDIRECTORY})
|
||||||
|
52
src/accounts/hatchet/CMakeLists.txt
Normal file
52
src/accounts/hatchet/CMakeLists.txt
Normal 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}
|
||||||
|
)
|
||||||
|
|
417
src/accounts/hatchet/account/HatchetAccount.cpp
Normal file
417
src/accounts/hatchet/account/HatchetAccount.cpp
Normal 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 )
|
132
src/accounts/hatchet/account/HatchetAccount.h
Normal file
132
src/accounts/hatchet/account/HatchetAccount.h
Normal 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
|
182
src/accounts/hatchet/account/HatchetAccountConfig.cpp
Normal file
182
src/accounts/hatchet/account/HatchetAccountConfig.cpp
Normal 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 );
|
||||||
|
}
|
69
src/accounts/hatchet/account/HatchetAccountConfig.h
Normal file
69
src/accounts/hatchet/account/HatchetAccountConfig.h
Normal 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
|
150
src/accounts/hatchet/account/HatchetAccountConfig.ui
Normal file
150
src/accounts/hatchet/account/HatchetAccountConfig.ui
Normal 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>
|
14
src/accounts/hatchet/admin/certs/dreamcatcher.pem
Normal file
14
src/accounts/hatchet/admin/certs/dreamcatcher.pem
Normal 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-----
|
14
src/accounts/hatchet/admin/certs/mandella.pem
Normal file
14
src/accounts/hatchet/admin/certs/mandella.pem
Normal 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-----
|
44
src/accounts/hatchet/admin/certs/startcomroot.pem
Normal file
44
src/accounts/hatchet/admin/certs/startcomroot.pem
Normal 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-----
|
BIN
src/accounts/hatchet/admin/icons/hatchet-icon-512x512.png
Normal file
BIN
src/accounts/hatchet/admin/icons/hatchet-icon-512x512.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 28 KiB |
37
src/accounts/hatchet/cmake/Modules/FindQCA2.cmake
Normal file
37
src/accounts/hatchet/cmake/Modules/FindQCA2.cmake
Normal 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)
|
8
src/accounts/hatchet/resources.qrc
Normal file
8
src/accounts/hatchet/resources.qrc
Normal 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>
|
605
src/accounts/hatchet/sip/HatchetSip.cpp
Normal file
605
src/accounts/hatchet/sip/HatchetSip.cpp
Normal 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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
95
src/accounts/hatchet/sip/HatchetSip.h
Normal file
95
src/accounts/hatchet/sip/HatchetSip.h
Normal 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
|
324
src/accounts/hatchet/sip/WebSocket.cpp
Normal file
324
src/accounts/hatchet/sip/WebSocket.cpp
Normal 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 );
|
||||||
|
}
|
84
src/accounts/hatchet/sip/WebSocket.h
Normal file
84
src/accounts/hatchet/sip/WebSocket.h
Normal 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
|
71
src/accounts/hatchet/sip/WebSocketThreadController.cpp
Normal file
71
src/accounts/hatchet/sip/WebSocketThreadController.cpp
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
49
src/accounts/hatchet/sip/WebSocketThreadController.h
Normal file
49
src/accounts/hatchet/sip/WebSocketThreadController.h
Normal 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
|
72
src/accounts/hatchet/sip/hatchet_config.hpp
Normal file
72
src/accounts/hatchet/sip/hatchet_config.hpp
Normal 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
|
@@ -70,8 +70,12 @@ Source::Source( int id, const QString& nodeId )
|
|||||||
|
|
||||||
if ( m_isLocal )
|
if ( m_isLocal )
|
||||||
{
|
{
|
||||||
connect( Accounts::AccountManager::instance(), SIGNAL( connected( Tomahawk::Accounts::Account* ) ), SLOT( setOnline() ) );
|
connect( Accounts::AccountManager::instance(),
|
||||||
connect( Accounts::AccountManager::instance(), SIGNAL( disconnected( Tomahawk::Accounts::Account* ) ), SLOT( setOffline() ) );
|
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 );
|
emit collectionRemoved( c );
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
Source::handleDisconnect( Tomahawk::Accounts::Account*, Tomahawk::Accounts::AccountManager::DisconnectReason reason )
|
||||||
|
{
|
||||||
|
if ( reason == Tomahawk::Accounts::AccountManager::Disabled )
|
||||||
|
setOffline();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
void
|
void
|
||||||
Source::setOffline()
|
Source::setOffline()
|
||||||
|
@@ -26,6 +26,7 @@
|
|||||||
#include <QtCore/QVariantMap>
|
#include <QtCore/QVariantMap>
|
||||||
|
|
||||||
#include "Typedefs.h"
|
#include "Typedefs.h"
|
||||||
|
#include "accounts/AccountManager.h"
|
||||||
#include "network/ControlConnection.h"
|
#include "network/ControlConnection.h"
|
||||||
#include "network/DbSyncConnection.h"
|
#include "network/DbSyncConnection.h"
|
||||||
#include "collection/Collection.h"
|
#include "collection/Collection.h"
|
||||||
@@ -136,6 +137,7 @@ private slots:
|
|||||||
void dbLoaded( unsigned int id, const QString& fname );
|
void dbLoaded( unsigned int id, const QString& fname );
|
||||||
void updateIndexWhenSynced();
|
void updateIndexWhenSynced();
|
||||||
|
|
||||||
|
void handleDisconnect( Tomahawk::Accounts::Account*, Tomahawk::Accounts::AccountManager::DisconnectReason reason );
|
||||||
void setOffline();
|
void setOffline();
|
||||||
void setOnline();
|
void setOnline();
|
||||||
|
|
||||||
|
@@ -512,7 +512,8 @@ AccountManager::onStateChanged( Account::ConnectionState state )
|
|||||||
if ( account->connectionState() == Account::Disconnected )
|
if ( account->connectionState() == Account::Disconnected )
|
||||||
{
|
{
|
||||||
m_connectedAccounts.removeAll( account );
|
m_connectedAccounts.removeAll( account );
|
||||||
emit disconnected( account );
|
DisconnectReason reason = account->enabled() ? Disconnected : Disabled;
|
||||||
|
emit disconnected( account, reason );
|
||||||
}
|
}
|
||||||
else if ( account->connectionState() == Account::Connected )
|
else if ( account->connectionState() == Account::Connected )
|
||||||
{
|
{
|
||||||
|
@@ -21,7 +21,8 @@
|
|||||||
#ifndef ACCOUNTMANAGER_H
|
#ifndef ACCOUNTMANAGER_H
|
||||||
#define ACCOUNTMANAGER_H
|
#define ACCOUNTMANAGER_H
|
||||||
|
|
||||||
#include <QtCore/QObject>
|
#include <QFlags>
|
||||||
|
#include <QObject>
|
||||||
|
|
||||||
#include "Typedefs.h"
|
#include "Typedefs.h"
|
||||||
#include "DllMacro.h"
|
#include "DllMacro.h"
|
||||||
@@ -42,6 +43,11 @@ class DLLEXPORT AccountManager : public QObject
|
|||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
|
|
||||||
public:
|
public:
|
||||||
|
enum DisconnectReason {
|
||||||
|
Disconnected,
|
||||||
|
Disabled
|
||||||
|
};
|
||||||
|
|
||||||
static AccountManager* instance();
|
static AccountManager* instance();
|
||||||
|
|
||||||
explicit AccountManager( QObject *parent );
|
explicit AccountManager( QObject *parent );
|
||||||
@@ -61,7 +67,7 @@ public:
|
|||||||
void hookupAndEnable( Account* account, bool startup = false ); /// Hook up signals and start the plugin
|
void hookupAndEnable( Account* account, bool startup = false ); /// Hook up signals and start the plugin
|
||||||
void removeAccount( Account* account );
|
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* > accounts( Tomahawk::Accounts::AccountType type ) const { return m_accountsByAccountType[ type ]; }
|
||||||
|
|
||||||
QList< Account* > accountsFromFactory( Tomahawk::Accounts::AccountFactory* factory ) const;
|
QList< Account* > accountsFromFactory( Tomahawk::Accounts::AccountFactory* factory ) const;
|
||||||
@@ -105,7 +111,7 @@ signals:
|
|||||||
void removed( Tomahawk::Accounts::Account* );
|
void removed( Tomahawk::Accounts::Account* );
|
||||||
|
|
||||||
void connected( 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 authError( Tomahawk::Accounts::Account* );
|
||||||
|
|
||||||
void stateChanged( Account* p, Accounts::Account::ConnectionState state );
|
void stateChanged( Account* p, Accounts::Account::ConnectionState state );
|
||||||
|
@@ -54,7 +54,7 @@ Closure::Closure(QObject* sender,
|
|||||||
|
|
||||||
Closure::Closure(QObject* sender,
|
Closure::Closure(QObject* sender,
|
||||||
const char* signal,
|
const char* signal,
|
||||||
std::tr1::function<void()> callback)
|
function<void()> callback)
|
||||||
: callback_(callback) {
|
: callback_(callback) {
|
||||||
Connect(sender, signal);
|
Connect(sender, signal);
|
||||||
}
|
}
|
||||||
|
@@ -21,7 +21,13 @@
|
|||||||
|
|
||||||
#include "DllMacro.h"
|
#include "DllMacro.h"
|
||||||
|
|
||||||
|
#ifdef _WEBSOCKETPP_CPP11_STL_
|
||||||
|
#include <functional>
|
||||||
|
using std::function;
|
||||||
|
#else
|
||||||
#include <tr1/functional>
|
#include <tr1/functional>
|
||||||
|
using std::tr1::function;
|
||||||
|
#endif
|
||||||
|
|
||||||
#include <QMetaMethod>
|
#include <QMetaMethod>
|
||||||
#include <QObject>
|
#include <QObject>
|
||||||
@@ -64,7 +70,7 @@ class DLLEXPORT Closure : public QObject, boost::noncopyable {
|
|||||||
const ClosureArgumentWrapper* val3 = 0);
|
const ClosureArgumentWrapper* val3 = 0);
|
||||||
|
|
||||||
Closure(QObject* sender, const char* signal,
|
Closure(QObject* sender, const char* signal,
|
||||||
std::tr1::function<void()> callback);
|
function<void()> callback);
|
||||||
|
|
||||||
void setAutoDelete( bool autoDelete ) { autoDelete_ = autoDelete; }
|
void setAutoDelete( bool autoDelete ) { autoDelete_ = autoDelete; }
|
||||||
|
|
||||||
@@ -87,7 +93,7 @@ class DLLEXPORT Closure : public QObject, boost::noncopyable {
|
|||||||
void Connect(QObject* sender, const char* signal);
|
void Connect(QObject* sender, const char* signal);
|
||||||
|
|
||||||
QMetaMethod slot_;
|
QMetaMethod slot_;
|
||||||
std::tr1::function<void()> callback_;
|
function<void()> callback_;
|
||||||
bool autoDelete_;
|
bool autoDelete_;
|
||||||
QObject* outOfThreadReceiver_;
|
QObject* outOfThreadReceiver_;
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user