This commit is contained in:
John Maguire 2013-01-11 11:08:22 +01:00
commit 22322593aa
12 changed files with 257 additions and 54 deletions

View File

@ -16,6 +16,7 @@ enum MsgType {
STOP = 23; STOP = 23;
NEXT = 24; NEXT = 24;
PREV = 25; PREV = 25;
TOOGLE_SHUFFLE = 26;
// Messages send from server to client // Messages send from server to client
INFOS = 40; INFOS = 40;
@ -33,13 +34,14 @@ enum EngineState {
} }
message Message { message Message {
required MsgType msgType = 1; required int32 version = 1;
required MsgType msgType = 2;
optional EngineState state = 2; optional EngineState state = 3;
optional ClementineInfos infos = 3; optional ClementineInfos infos = 4;
optional SongMetadata currentSong = 4; optional SongMetadata currentSong = 5;
optional int32 volume = 5; optional int32 volume = 6;
repeated Playlist playlists = 6; repeated Playlist playlists = 7;
} }
message ClementineInfos { message ClementineInfos {

View File

@ -218,6 +218,7 @@ set(SOURCES
networkremote/networkremote.cpp networkremote/networkremote.cpp
networkremote/incomingdataparser.cpp networkremote/incomingdataparser.cpp
networkremote/outgoingdatacreator.cpp networkremote/outgoingdatacreator.cpp
networkremote/remoteclient.cpp
playlist/dynamicplaylistcontrols.cpp playlist/dynamicplaylistcontrols.cpp
playlist/playlist.cpp playlist/playlist.cpp
@ -496,6 +497,7 @@ set(HEADERS
networkremote/networkremote.h networkremote/networkremote.h
networkremote/incomingdataparser.h networkremote/incomingdataparser.h
networkremote/outgoingdatacreator.h networkremote/outgoingdatacreator.h
networkremote/remoteclient.h
playlist/dynamicplaylistcontrols.h playlist/dynamicplaylistcontrols.h
playlist/playlist.h playlist/playlist.h

View File

@ -43,6 +43,8 @@ IncomingDataParser::IncomingDataParser(Application* app)
app_->player(), SLOT(PlayAt(int,Engine::TrackChangeFlags,bool))); app_->player(), SLOT(PlayAt(int,Engine::TrackChangeFlags,bool)));
connect(this, SIGNAL(SetActivePlaylist(int)), connect(this, SIGNAL(SetActivePlaylist(int)),
app_->playlist_manager(), SLOT(SetActivePlaylist(int))); app_->playlist_manager(), SLOT(SetActivePlaylist(int)));
connect(this, SIGNAL(ShuffleCurrent()),
app_->playlist_manager(), SLOT(ShuffleCurrent()));
} }
IncomingDataParser::~IncomingDataParser() { IncomingDataParser::~IncomingDataParser() {
@ -52,13 +54,12 @@ bool IncomingDataParser::close_connection() {
return close_connection_; return close_connection_;
} }
void IncomingDataParser::Parse(const QByteArray& b64_data) { void IncomingDataParser::Parse(const QByteArray& data) {
close_connection_ = false; close_connection_ = false;
QByteArray pb_data = QByteArray::fromBase64(b64_data);
// Parse the incoming data // Parse the incoming data
pb::remote::Message msg; pb::remote::Message msg;
if (!msg.ParseFromArray(pb_data.constData(), pb_data.size())) { if (!msg.ParseFromArray(data.constData(), data.size())) {
qLog(Info) << "Couldn't parse data"; qLog(Info) << "Couldn't parse data";
return; return;
} }
@ -90,6 +91,8 @@ void IncomingDataParser::Parse(const QByteArray& b64_data) {
break; break;
case pb::remote::CHANGE_SONG: ChangeSong(&msg); case pb::remote::CHANGE_SONG: ChangeSong(&msg);
break; break;
case pb::remote::TOOGLE_SHUFFLE: emit ShuffleCurrent();
break;
default: break; default: break;
} }
} }

View File

@ -11,9 +11,11 @@ public:
IncomingDataParser(Application* app); IncomingDataParser(Application* app);
~IncomingDataParser(); ~IncomingDataParser();
void Parse(const QByteArray& pb_data);
bool close_connection(); bool close_connection();
public slots:
void Parse(const QByteArray& pb_data);
signals: signals:
void SendClementineInfos(); void SendClementineInfos();
void SendFirstData(); void SendFirstData();
@ -29,6 +31,7 @@ signals:
void SetVolume(int volume); void SetVolume(int volume);
void PlayAt(int i, Engine::TrackChangeFlags change, bool reshuffle); void PlayAt(int i, Engine::TrackChangeFlags change, bool reshuffle);
void SetActivePlaylist(int id); void SetActivePlaylist(int id);
void ShuffleCurrent();
private: private:
Application* app_; Application* app_;

View File

@ -26,15 +26,17 @@
const char* NetworkRemote::kSettingsGroup = "NetworkRemote"; const char* NetworkRemote::kSettingsGroup = "NetworkRemote";
const int NetworkRemote::kDefaultServerPort = 5500; const int NetworkRemote::kDefaultServerPort = 5500;
const int NetworkRemote::kProtocolBufferVersion = 1;
NetworkRemote::NetworkRemote(Application* app) NetworkRemote::NetworkRemote(Application* app)
: app_(app) : app_(app)
{ {
signals_connected_ = false;
} }
NetworkRemote::~NetworkRemote() { NetworkRemote::~NetworkRemote() {
server_->close(); StopServer();
delete incoming_data_parser_; delete incoming_data_parser_;
delete outgoing_data_creator_; delete outgoing_data_creator_;
} }
@ -45,6 +47,14 @@ void NetworkRemote::ReadSettings() {
s.beginGroup(NetworkRemote::kSettingsGroup); s.beginGroup(NetworkRemote::kSettingsGroup);
use_remote_ = s.value("use_remote").toBool(); use_remote_ = s.value("use_remote").toBool();
port_ = s.value("port").toInt(); port_ = s.value("port").toInt();
// Use only non public ips must be true be default
if (s.contains("only_non_public_ip")) {
only_non_public_ip_ = s.value("only_non_public_ip").toBool();
} else {
only_non_public_ip_ = true;
}
if (port_ == 0) { if (port_ == 0) {
port_ = kDefaultServerPort; port_ = kDefaultServerPort;
} }
@ -53,9 +63,12 @@ void NetworkRemote::ReadSettings() {
void NetworkRemote::SetupServer() { void NetworkRemote::SetupServer() {
server_ = new QTcpServer(); server_ = new QTcpServer();
server_ipv6_ = new QTcpServer();
incoming_data_parser_ = new IncomingDataParser(app_); incoming_data_parser_ = new IncomingDataParser(app_);
outgoing_data_creator_ = new OutgoingDataCreator(app_); outgoing_data_creator_ = new OutgoingDataCreator(app_);
outgoing_data_creator_->SetClients(&clients_);
connect(app_->current_art_loader(), connect(app_->current_art_loader(),
SIGNAL(ArtLoaded(const Song&, const QString&, const QImage&)), SIGNAL(ArtLoaded(const Song&, const QString&, const QImage&)),
outgoing_data_creator_, outgoing_data_creator_,
@ -76,11 +89,11 @@ void NetworkRemote::StartServer() {
qLog(Info) << "Starting network remote"; qLog(Info) << "Starting network remote";
clients_ = NULL;
connect(server_, SIGNAL(newConnection()), this, SLOT(AcceptConnection())); connect(server_, SIGNAL(newConnection()), this, SLOT(AcceptConnection()));
connect(server_ipv6_, SIGNAL(newConnection()), this, SLOT(AcceptConnection()));
server_->listen(QHostAddress::Any, port_); server_->listen(QHostAddress::Any, port_);
server_ipv6_->listen(QHostAddress::AnyIPv6, port_);
qLog(Info) << "Listening on port " << port_; qLog(Info) << "Listening on port " << port_;
} }
@ -88,6 +101,8 @@ void NetworkRemote::StartServer() {
void NetworkRemote::StopServer() { void NetworkRemote::StopServer() {
if (server_->isListening()) { if (server_->isListening()) {
server_->close(); server_->close();
server_ipv6_->close();
clients_.clear();
} }
} }
@ -97,10 +112,8 @@ void NetworkRemote::ReloadSettings() {
} }
void NetworkRemote::AcceptConnection() { void NetworkRemote::AcceptConnection() {
if (!clients_) { if (!signals_connected_) {
// Create a new QList with clients signals_connected_ = true;
clients_ = new QList<QTcpSocket*>();
outgoing_data_creator_->SetClients(clients_);
// Setting up the signals, but only once // Setting up the signals, but only once
connect(incoming_data_parser_, SIGNAL(SendClementineInfos()), connect(incoming_data_parser_, SIGNAL(SendClementineInfos()),
@ -122,23 +135,50 @@ void NetworkRemote::AcceptConnection() {
connect(app_->player()->engine(), SIGNAL(StateChanged(Engine::State)), connect(app_->player()->engine(), SIGNAL(StateChanged(Engine::State)),
outgoing_data_creator_, SLOT(StateChanged(Engine::State))); outgoing_data_creator_, SLOT(StateChanged(Engine::State)));
} }
QTcpSocket* client = server_->nextPendingConnection();
clients_->push_back(client); if (server_->hasPendingConnections()) {
QTcpSocket* client_socket = server_->nextPendingConnection();
// Check if our ip is in private scope
if (only_non_public_ip_
&& !IpIsPrivate(client_socket->peerAddress().toIPv4Address())) {
qLog(Info) << "Got a connection from public ip" <<
client_socket->peerAddress().toString();
} else {
CreateRemoteClient(client_socket);
// TODO: Check private ips for ipv6
}
// Connect to the slot IncomingData when receiving data } else {
connect(client, SIGNAL(readyRead()), this, SLOT(IncomingData())); // No checks on ipv6
} CreateRemoteClient(server_ipv6_->nextPendingConnection());
}
void NetworkRemote::IncomingData() { }
QTcpSocket* client = static_cast<QTcpSocket*>(QObject::sender());
bool NetworkRemote::IpIsPrivate(int ip) {
// Now read all the data from the socket int private_local = QHostAddress("127.0.0.1").toIPv4Address();
QByteArray data; int private_a = QHostAddress("10.0.0.0").toIPv4Address();
data = client->readAll(); int private_b = QHostAddress("172.16.0.0").toIPv4Address();
incoming_data_parser_->Parse(data); int private_c = QHostAddress("192.168.0.0").toIPv4Address();
if (incoming_data_parser_->close_connection()) { // Check if we have a private ip address
client->close(); if (ip == private_local
|| (ip >= private_a && ip < private_a + 16777216)
|| (ip >= private_b && ip < private_b + 1048576)
|| (ip >= private_c && ip < private_c + 65536)) {
return true;
} else {
return false;
}
}
void NetworkRemote::CreateRemoteClient(QTcpSocket *client_socket) {
if (client_socket) {
// Add the client to the list
RemoteClient* client = new RemoteClient(app_, client_socket);
clients_.push_back(client);
// Connect the signal to parse data
connect(client, SIGNAL(Parse(QByteArray)),
incoming_data_parser_, SLOT(Parse(QByteArray)));
} }
} }

View File

@ -1,7 +1,6 @@
#ifndef NETWORKREMOTE_H #ifndef NETWORKREMOTE_H
#define NETWORKREMOTE_H #define NETWORKREMOTE_H
#include <QtNetwork>
#include <QTcpServer> #include <QTcpServer>
#include <QTcpSocket> #include <QTcpSocket>
@ -9,12 +8,14 @@
#include "core/application.h" #include "core/application.h"
#include "incomingdataparser.h" #include "incomingdataparser.h"
#include "outgoingdatacreator.h" #include "outgoingdatacreator.h"
#include "remoteclient.h"
class NetworkRemote : public QThread { class NetworkRemote : public QThread {
Q_OBJECT Q_OBJECT
public: public:
static const char* kSettingsGroup; static const char* kSettingsGroup;
static const int kDefaultServerPort; static const int kDefaultServerPort;
static const int kProtocolBufferVersion;
NetworkRemote(Application* app); NetworkRemote(Application* app);
~NetworkRemote(); ~NetworkRemote();
@ -24,19 +25,24 @@ public slots:
void StartServer(); void StartServer();
void ReloadSettings(); void ReloadSettings();
void AcceptConnection(); void AcceptConnection();
void IncomingData();
private: private:
QTcpServer* server_; QTcpServer* server_;
QList<QTcpSocket*>* clients_; QTcpServer* server_ipv6_;
IncomingDataParser* incoming_data_parser_; IncomingDataParser* incoming_data_parser_;
OutgoingDataCreator* outgoing_data_creator_; OutgoingDataCreator* outgoing_data_creator_;
int port_; int port_;
bool use_remote_; bool use_remote_;
bool only_non_public_ip_;
bool signals_connected_;
Application* app_; Application* app_;
QList<RemoteClient*> clients_;
void StopServer(); void StopServer();
void ReadSettings(); void ReadSettings();
void CreateRemoteClient(QTcpSocket* client_socket);
bool IpIsPrivate(int ip);
}; };
#endif // NETWORKREMOTE_H #endif // NETWORKREMOTE_H

View File

@ -16,11 +16,11 @@
*/ */
#include "outgoingdatacreator.h" #include "outgoingdatacreator.h"
#include "networkremote.h"
#include "core/logging.h" #include "core/logging.h"
OutgoingDataCreator::OutgoingDataCreator(Application* app) OutgoingDataCreator::OutgoingDataCreator(Application* app)
: app_(app), : app_(app)
clients_(NULL)
{ {
// Create Keep Alive Timer // Create Keep Alive Timer
keep_alive_timer_ = new QTimer(this); keep_alive_timer_ = new QTimer(this);
@ -31,7 +31,7 @@ OutgoingDataCreator::OutgoingDataCreator(Application* app)
OutgoingDataCreator::~OutgoingDataCreator() { OutgoingDataCreator::~OutgoingDataCreator() {
} }
void OutgoingDataCreator::SetClients(QList<QTcpSocket*>* clients) { void OutgoingDataCreator::SetClients(QList<RemoteClient*>* clients) {
clients_ = clients; clients_ = clients;
// After we got some clients, start the keep alive timer // After we got some clients, start the keep alive timer
// Default: every 10 seconds // Default: every 10 seconds
@ -40,21 +40,21 @@ void OutgoingDataCreator::SetClients(QList<QTcpSocket*>* clients) {
void OutgoingDataCreator::SendDataToClients(pb::remote::Message* msg) { void OutgoingDataCreator::SendDataToClients(pb::remote::Message* msg) {
// Check if we have clients to send data to // Check if we have clients to send data to
if (!clients_ || clients_->size() == 0) { if (clients_->empty()) {
return; return;
} }
QTcpSocket* sock; // Add the Version number
foreach(sock, *clients_) { msg->set_version(NetworkRemote::kProtocolBufferVersion);
RemoteClient* client;
foreach(client, *clients_) {
// Check if the client is still active // Check if the client is still active
if (sock->state() == QTcpSocket::ConnectedState) { if (client->State() == QTcpSocket::ConnectedState) {
std::string data = msg->SerializeAsString(); client->SendData(msg);
QByteArray b64_data = QByteArray::fromRawData(data.data(), data.length());
sock->write(b64_data.toBase64());
sock->write("\n");
sock->flush();
} else { } else {
clients_->removeAt(clients_->indexOf(sock)); clients_->removeAt(clients_->indexOf(client));
delete client;
} }
} }
} }
@ -137,7 +137,7 @@ void OutgoingDataCreator::CurrentSongChanged(const Song& song, const QString& ur
current_uri_ = uri; current_uri_ = uri;
current_image_ = img; current_image_ = img;
if (clients_) { if (!clients_->empty()) {
// Create the message // Create the message
pb::remote::Message msg; pb::remote::Message msg;
msg.set_msgtype(pb::remote::CURRENT_METAINFOS); msg.set_msgtype(pb::remote::CURRENT_METAINFOS);

View File

@ -13,6 +13,7 @@
#include "playlist/playlist.h" #include "playlist/playlist.h"
#include "playlist/playlistmanager.h" #include "playlist/playlistmanager.h"
#include "remotecontrolmessages.pb.h" #include "remotecontrolmessages.pb.h"
#include "remoteclient.h"
class OutgoingDataCreator : public QObject { class OutgoingDataCreator : public QObject {
Q_OBJECT Q_OBJECT
@ -20,7 +21,7 @@ public:
OutgoingDataCreator(Application* app); OutgoingDataCreator(Application* app);
~OutgoingDataCreator(); ~OutgoingDataCreator();
void SetClients(QList<QTcpSocket*>* clients); void SetClients(QList<RemoteClient*>* clients);
public slots: public slots:
void SendClementineInfos(); void SendClementineInfos();
@ -36,7 +37,7 @@ public slots:
private: private:
Application* app_; Application* app_;
QList<QTcpSocket*>* clients_; QList<RemoteClient*>* clients_;
Song current_song_; Song current_song_;
QString current_uri_; QString current_uri_;
QImage current_image_; QImage current_image_;

View File

@ -0,0 +1,83 @@
/* This file is part of Clementine.
Copyright 2013, Andreas Muttscheller <asfa194@gmail.com>
Clementine 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.
Clementine 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 Clementine. If not, see <http://www.gnu.org/licenses/>.
*/
#include "core/logging.h"
#include "remoteclient.h"
#include <QDataStream>
RemoteClient::RemoteClient(Application* app, QTcpSocket* client)
: app_(app),
client_(client)
{
// Open the buffer
buffer_.setData(QByteArray());
buffer_.open(QIODevice::ReadWrite);
reading_protobuf_ = false;
// Connect to the slot IncomingData when receiving data
connect(client, SIGNAL(readyRead()), this, SLOT(IncomingData()));
}
RemoteClient::~RemoteClient() {
}
void RemoteClient::IncomingData() {
while (client_->bytesAvailable()) {
if (!reading_protobuf_) {
// Read the length of the next message
QDataStream s(client_);
s >> expected_length_;
reading_protobuf_ = true;
}
// Read some of the message
buffer_.write(
client_->read(expected_length_ - buffer_.size()));
// Did we get everything?
if (buffer_.size() == expected_length_) {
// Parse the message
emit Parse(buffer_.data());
// Clear the buffer
buffer_.close();
buffer_.setData(QByteArray());
buffer_.open(QIODevice::ReadWrite);
reading_protobuf_ = false;
}
}
}
void RemoteClient::SendData(pb::remote::Message *msg) {
// Serialize the message
std::string data = msg->SerializeAsString();
// write the length of the data first
QDataStream s(client_);
s << qint32(data.length());
s.writeRawData(data.data(), data.length());
// Flush data
client_->flush();
}
QAbstractSocket::SocketState RemoteClient::State() {
return client_->state();
}

View File

@ -0,0 +1,35 @@
#ifndef REMOTECLIENT_H
#define REMOTECLIENT_H
#include <QAbstractSocket>
#include <QTcpSocket>
#include <QBuffer>
#include "core/application.h"
#include "incomingdataparser.h"
class RemoteClient : public QObject {
Q_OBJECT
public:
RemoteClient(Application* app, QTcpSocket* client);
~RemoteClient();
void SendData(pb::remote::Message* msg);
QAbstractSocket::SocketState State();
private slots:
void IncomingData();
signals:
void Parse(const QByteArray& pb_data);
private:
Application* app_;
QTcpSocket* client_;
bool reading_protobuf_;
quint32 expected_length_;
QBuffer buffer_;
};
#endif // REMOTECLIENT_H

View File

@ -51,6 +51,13 @@ void NetworkRemoteSettingsPage::Load() {
} }
ui_->use_remote->setChecked(s.value("use_remote").toBool()); ui_->use_remote->setChecked(s.value("use_remote").toBool());
if (s.contains("only_non_public_ip")) {
ui_->only_non_public_ip->setChecked(s.value("only_non_public_ip").toBool());
} else {
// Default yes
ui_->only_non_public_ip->setChecked(true);
s.setValue("only_non_public_ip", true);
}
s.endGroup(); s.endGroup();
} }
@ -61,6 +68,7 @@ void NetworkRemoteSettingsPage::Save() {
s.beginGroup(NetworkRemote::kSettingsGroup); s.beginGroup(NetworkRemote::kSettingsGroup);
s.setValue("port", ui_->remote_port->value()); s.setValue("port", ui_->remote_port->value());
s.setValue("use_remote", ui_->use_remote->isChecked()); s.setValue("use_remote", ui_->use_remote->isChecked());
s.setValue("only_non_public_ip", ui_->only_non_public_ip->isChecked());
s.endGroup(); s.endGroup();

View File

@ -6,7 +6,7 @@
<rect> <rect>
<x>0</x> <x>0</x>
<y>0</y> <y>0</y>
<width>400</width> <width>475</width>
<height>300</height> <height>300</height>
</rect> </rect>
</property> </property>
@ -33,7 +33,7 @@
<item> <item>
<layout class="QHBoxLayout" name="horizontalLayout_5"> <layout class="QHBoxLayout" name="horizontalLayout_5">
<item> <item>
<widget class="QLabel" name="label_15"> <widget class="QLabel" name="label_remote_port">
<property name="minimumSize"> <property name="minimumSize">
<size> <size>
<width>171</width> <width>171</width>
@ -63,6 +63,26 @@
</item> </item>
</layout> </layout>
</item> </item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<widget class="QCheckBox" name="only_non_public_ip">
<property name="toolTip">
<string>Only accept connections from clients within the ip ranges:
10.x.x.x
172.16.0.0 - 172.31.255.255
192.168.x.x</string>
</property>
<property name="text">
<string>Accept non public clients only</string>
</property>
<property name="checked">
<bool>true</bool>
</property>
</widget>
</item>
</layout>
</item>
</layout> </layout>
</widget> </widget>
</item> </item>