Network remote can now send songs to clients.

This commit is contained in:
Andreas 2013-07-12 12:31:27 +02:00
parent cd85b67ebc
commit c09d77f413
10 changed files with 206 additions and 11 deletions

View File

@ -15,6 +15,7 @@ enum MsgType {
OPEN_PLAYLIST = 10;
CLOSE_PLAYLIST = 11;
GET_LYRICS = 14;
DOWNLOAD_SONGS = 15;
// Lastfm
LOVE = 12;
BAN = 13;
@ -44,6 +45,7 @@ enum MsgType {
ACTIVE_PLAYLIST_CHANGED = 47;
FIRST_DATA_SENT_COMPLETE = 48;
LYRICS = 49;
SONG_FILE_CHUNK = 50;
}
// Valid Engine states
@ -70,6 +72,9 @@ message SongMetadata {
optional string pretty_length = 12;
optional bytes art = 13;
optional int32 length = 14;
optional bool is_local = 15;
optional string filename = 16;
optional int32 file_size = 17;
}
// Playlist informations
@ -167,6 +172,7 @@ message ResponseUpdateTrackPosition {
message RequestConnect {
optional int32 auth_code = 1;
optional bool send_playlist_songs = 2;
optional bool downloader = 3;
}
// Respone, why the connection was closed
@ -174,6 +180,7 @@ enum ReasonDisconnect {
Server_Shutdown = 1;
Wrong_Auth_Code = 2;
Not_Authenticated = 3;
Download_Forbidden = 4;
}
message ResponseDisconnect {
optional ReasonDisconnect reason_disconnect = 1;
@ -224,9 +231,30 @@ message Lyric {
optional string content = 3;
}
// Message request for downloading songs
enum DownloadItem {
CurrentItem = 1;
ItemAlbum = 2;
APlaylist = 3;
}
message RequestDownloadSongs {
optional DownloadItem download_item = 1;
optional int32 playlist_id = 2;
}
message ResponseSongFileChunk {
optional int32 chunk_number = 1;
optional int32 chunk_count = 2;
optional int32 file_number = 3;
optional int32 file_count = 4;
optional SongMetadata song_metadata = 6; // only sent with first chunk!
optional bytes data = 7;
optional int32 size = 8;
}
// The message itself
message Message {
optional int32 version = 1 [default=8];
optional int32 version = 1 [default=9];
optional MsgType type = 2 [default=UNKNOWN]; // What data is in the message?
optional RequestConnect request_connect = 21;
@ -239,6 +267,7 @@ message Message {
optional RequestRemoveSongs request_remove_songs = 26;
optional RequestOpenPlaylist request_open_playlist = 28;
optional RequestClosePlaylist request_close_playlist = 29;
optional RequestDownloadSongs request_download_songs = 31;
optional Repeat repeat = 13;
optional Shuffle shuffle = 14;
@ -252,4 +281,5 @@ message Message {
optional ResponseDisconnect response_disconnect = 22;
optional ResponseActiveChanged response_active_changed = 24;
optional ResponseLyrics response_lyrics = 30;
optional ResponseSongFileChunk response_song_file_chunk = 32;
}

View File

@ -93,9 +93,11 @@ bool IncomingDataParser::close_connection() {
void IncomingDataParser::Parse(const pb::remote::Message& msg) {
close_connection_ = false;
RemoteClient* client = qobject_cast<RemoteClient*>(sender());
// Now check what's to do
switch (msg.type()) {
case pb::remote::CONNECT: ClientConnect(msg);
case pb::remote::CONNECT: ClientConnect(client, msg);
break;
case pb::remote::DISCONNECT: close_connection_ = true;
break;
@ -144,6 +146,9 @@ void IncomingDataParser::Parse(const pb::remote::Message& msg) {
break;
case pb::remote::GET_LYRICS: emit GetLyrics();
break;
case pb::remote::DOWNLOAD_SONGS:
emit SendSongs(msg.request_download_songs(), client);
break;
default: break;
}
}
@ -235,7 +240,8 @@ void IncomingDataParser::RemoveSongs(const pb::remote::Message& msg) {
emit RemoveSongs(songs);
}
void IncomingDataParser::ClientConnect(const pb::remote::Message& msg) {
void IncomingDataParser::ClientConnect(RemoteClient* client, const pb::remote::Message& msg) {
// Always sned the Clementine infos
emit SendClementineInfo();

View File

@ -4,6 +4,7 @@
#include "core/player.h"
#include "core/application.h"
#include "remotecontrolmessages.pb.h"
#include "remoteclient.h"
class IncomingDataParser : public QObject {
Q_OBJECT
@ -43,6 +44,7 @@ signals:
void InsertUrls(const QList<QUrl>& urls, int pos, bool play_now, bool enqueue);
void RemoveSongs(const QList<int>& indices);
void SeekTo(int seconds);
void SendSongs(const pb::remote::RequestDownloadSongs& request, RemoteClient* client);
private:
Application* app_;
@ -54,7 +56,7 @@ private:
void SetShuffleMode(const pb::remote::Shuffle& shuffle);
void InsertUrls(const pb::remote::Message& msg);
void RemoveSongs(const pb::remote::Message& msg);
void ClientConnect(const pb::remote::Message& msg);
void ClientConnect(RemoteClient* client, const pb::remote::Message& msg);
void SendPlaylists(const pb::remote::Message& msg);
void OpenPlaylist(const pb::remote::Message& msg);
void ClosePlaylist(const pb::remote::Message& msg);

View File

@ -158,6 +158,11 @@ void NetworkRemote::AcceptConnection() {
connect(incoming_data_parser_.get(), SIGNAL(GetLyrics()),
outgoing_data_creator_.get(), SLOT(GetLyrics()));
connect(incoming_data_parser_.get(),
SIGNAL(SendSongs(pb::remote::RequestDownloadSongs,RemoteClient*)),
outgoing_data_creator_.get(),
SLOT(SendSongs(pb::remote::RequestDownloadSongs,RemoteClient*)));
}
QTcpServer* server = qobject_cast<QTcpServer*>(sender());

View File

@ -22,6 +22,9 @@
#include "networkremote.h"
#include "core/logging.h"
#include "core/timeconstants.h"
#include "library/librarybackend.h"
const quint32 OutgoingDataCreator::kFileChunkSize = 100000; // in Bytes
OutgoingDataCreator::OutgoingDataCreator(Application* app)
: app_(app),
@ -137,12 +140,17 @@ void OutgoingDataCreator::SendDataToClients(pb::remote::Message* msg) {
RemoteClient* client;
foreach(client, *clients_) {
// Do not send data to downloaders
if (client->isDownloader())
continue;
// Check if the client is still active
if (client->State() == QTcpSocket::ConnectedState) {
client->SendData(msg);
} else {
clients_->removeAt(clients_->indexOf(client));
delete client;
qDebug() << "Client deleted";
}
}
}
@ -334,6 +342,9 @@ void OutgoingDataCreator::CreateSong(
song_metadata->set_track(song.track());
song_metadata->set_disc(song.disc());
song_metadata->set_playcount(song.playcount());
song_metadata->set_is_local(song.url().isLocalFile());
song_metadata->set_filename(DataCommaSizeFromQString(song.basefilename()));
song_metadata->set_file_size(song.filesize());
// Append coverart
if (!art.isNull()) {
@ -529,3 +540,85 @@ void OutgoingDataCreator::SendLyrics(int id, const SongInfoFetcher::Result& resu
results_.take(id);
}
void OutgoingDataCreator::SendSongs(const pb::remote::RequestDownloadSongs &request,
RemoteClient* client) {
switch (request.download_item()) {
case pb::remote::CurrentItem:
SendSingleSong(client, current_song_, 1, 1);
break;
case pb::remote::ItemAlbum:
SendAlbum(client, current_song_);
break;
case pb::remote::APlaylist:
SendPlaylist(client, request.playlist_id());
break;
default:
break;
}
client->DisconnectClient(pb::remote::Server_Shutdown);
}
void OutgoingDataCreator::SendSingleSong(RemoteClient* client, const Song &song,
int song_no, int song_count) {
// Only local files!!!
if (!song.url().isLocalFile())
return;
// Calculate the number of chunks
int chunk_count = qRound((song.filesize() / kFileChunkSize) + 0.5);
int chunk_number = 1;
// Open the file
QFile file(song.url().toLocalFile());
file.open(QIODevice::ReadOnly);
while (!file.atEnd()) {
QByteArray data = file.read(kFileChunkSize);
pb::remote::Message msg;
msg.set_type(pb::remote::SONG_FILE_CHUNK);
pb::remote::ResponseSongFileChunk* chunk = msg.mutable_response_song_file_chunk();
chunk->set_chunk_count(chunk_count);
chunk->set_chunk_number(chunk_number);
chunk->set_file_count(song_count);
chunk->set_file_number(song_no);
chunk->set_size(file.size());
chunk->set_data(data.constData(), data.size());
if (chunk_number == 1) {
int i = app_->playlist_manager()->active()->current_row();
CreateSong(
song, song.image(), i,
msg.mutable_response_song_file_chunk()->mutable_song_metadata());
}
msg.set_version(msg.default_instance().version());
client->SendData(&msg);
chunk_number++;
}
file.close();
}
void OutgoingDataCreator::SendAlbum(RemoteClient *client, const Song &song) {
SongList album = app_->library_backend()->GetSongsByAlbum(song.album());
foreach (Song s, album) {
SendSingleSong(client, s, album.indexOf(s)+1, album.size());
}
}
void OutgoingDataCreator::SendPlaylist(RemoteClient *client, int playlist_id) {
Playlist* playlist = app_->playlist_manager()->playlist(playlist_id);
if(!playlist) {
qLog(Info) << "Could not find playlist with id = " << playlist_id;
return;
}
SongList song_list = playlist->GetAllSongs();
foreach (Song s, song_list) {
SendSingleSong(client, s, song_list.indexOf(s)+1, song_list.size());
}
}

View File

@ -24,7 +24,6 @@
#include "remoteclient.h"
#include <boost/scoped_ptr.hpp>
typedef QList<SongInfoProvider*> ProviderList;
class OutgoingDataCreator : public QObject {
@ -33,6 +32,8 @@ public:
OutgoingDataCreator(Application* app);
~OutgoingDataCreator();
static const quint32 kFileChunkSize;
void SetClients(QList<RemoteClient*>* clients);
public slots:
@ -57,6 +58,7 @@ public slots:
void DisconnectAllClients();
void GetLyrics();
void SendLyrics(int id, const SongInfoFetcher::Result& result);
void SendSongs(const pb::remote::RequestDownloadSongs& request, RemoteClient* client);
private:
Application* app_;
@ -83,6 +85,9 @@ private:
pb::remote::SongMetadata* song_metadata);
void CheckEnabledProviders();
SongInfoProvider* ProviderByName(const QString& name) const;
void SendSingleSong(RemoteClient* client, const Song& song, int song_no, int song_count);
void SendAlbum(RemoteClient* client, const Song& song);
void SendPlaylist(RemoteClient* client, int playlist_id);
};
#endif // OUTGOINGDATACREATOR_H

View File

@ -25,6 +25,7 @@
RemoteClient::RemoteClient(Application* app, QTcpSocket* client)
: app_(app),
downloader_(false),
client_(client)
{
// Open the buffer
@ -34,6 +35,8 @@ RemoteClient::RemoteClient(Application* app, QTcpSocket* client)
// Connect to the slot IncomingData when receiving data
connect(client, SIGNAL(readyRead()), this, SLOT(IncomingData()));
//connect(client, SIGNAL(disconnected()),
// client, SLOT(deleteLater()));
// Check if we use auth code
QSettings s;
@ -41,6 +44,7 @@ RemoteClient::RemoteClient(Application* app, QTcpSocket* client)
s.beginGroup(NetworkRemote::kSettingsGroup);
use_auth_code_ = s.value("use_auth_code", false).toBool();
auth_code_ = s.value("auth_code", 0).toInt();
allow_downloads_ = s.value("allow_downloads", false).toBool();
s.endGroup();
@ -50,6 +54,11 @@ RemoteClient::RemoteClient(Application* app, QTcpSocket* client)
RemoteClient::~RemoteClient() {
delete client_;
}
void RemoteClient::setDownloader(bool downloader) {
downloader_ = downloader;
}
void RemoteClient::IncomingData() {
@ -105,6 +114,24 @@ void RemoteClient::ParseMessage(const QByteArray &data) {
}
}
// Check if downloads are allowed
if (msg.type() == pb::remote::DOWNLOAD_SONGS && !allow_downloads_) {
DisconnectClient(pb::remote::Download_Forbidden);
return;
}
if (msg.type() == pb::remote::DISCONNECT) {
client_->flush();
client_->close();
qDebug() << "Client disconnected";
return;
}
if (msg.type() == pb::remote::DOWNLOAD_SONGS) {
qDebug() << "Downloader";
setDownloader(true);
}
// Check if the client has sent the correct auth code
if (!authenticated_) {
DisconnectClient(pb::remote::Not_Authenticated);
@ -132,19 +159,25 @@ void RemoteClient::DisconnectClient(pb::remote::ReasonDisconnect reason) {
// Sends data to client without check if authenticated
void RemoteClient::SendDataToClient(pb::remote::Message *msg) {
// Serialize the message
std::string data = msg->SerializeAsString();
// Check if we are still connected
if (client_->state() == QTcpSocket::ConnectedState) {
// 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());
if (downloader_) {
// Don't use QDataSteam for large files
client_->write(data.data(), data.length());
} else {
s.writeRawData(data.data(), data.length());
}
// Do NOT flush data here! If the client is already disconnected, it
// causes a SIGPIPE termination!!!
} else {
qDebug() << "Closed";
client_->close();
}
}

View File

@ -6,7 +6,7 @@
#include <QBuffer>
#include "core/application.h"
#include "incomingdataparser.h"
#include "remotecontrolmessages.pb.h"
class RemoteClient : public QObject {
Q_OBJECT
@ -17,6 +17,9 @@ public:
// This method checks if client is authenticated before sending the data
void SendData(pb::remote::Message* msg);
QAbstractSocket::SocketState State();
void setDownloader(bool downloader);
bool isDownloader() { return downloader_; }
void DisconnectClient(pb::remote::ReasonDisconnect reason);
private slots:
void IncomingData();
@ -26,7 +29,6 @@ signals:
private:
void ParseMessage(const QByteArray& data);
void DisconnectClient(pb::remote::ReasonDisconnect reason);
// Sends data to client without check if authenticated
void SendDataToClient(pb::remote::Message* msg);
@ -36,6 +38,8 @@ private:
bool use_auth_code_;
int auth_code_;
bool authenticated_;
bool allow_downloads_;
bool downloader_;
QTcpSocket* client_;
bool reading_protobuf_;

View File

@ -64,6 +64,8 @@ void NetworkRemoteSettingsPage::Load() {
ui_->use_auth_code->setChecked(s.value("use_auth_code", false).toBool());
ui_->auth_code->setValue(s.value("auth_code", qrand() % 100000).toInt());
ui_->allow_downloads->setChecked(s.value("allow_downloads", false).toBool());
s.endGroup();
QPixmap android_qr_code(":clementine_remote_qr.png");
@ -95,6 +97,7 @@ void NetworkRemoteSettingsPage::Save() {
s.setValue("only_non_public_ip", ui_->only_non_public_ip->isChecked());
s.setValue("use_auth_code", ui_->use_auth_code->isChecked());
s.setValue("auth_code", ui_->auth_code->value());
s.setValue("allow_downloads", ui_->allow_downloads->isChecked());
s.endGroup();

View File

@ -110,6 +110,20 @@
</item>
</layout>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout_4">
<item>
<widget class="QCheckBox" name="allow_downloads">
<property name="toolTip">
<string>Allow a client to download music from this computer.</string>
</property>
<property name="text">
<string>Allow downloads</string>
</property>
</widget>
</item>
</layout>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout_3">
<item>